nebu 0.1.16__py3-none-any.whl → 0.1.17__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.
@@ -1,9 +1,9 @@
1
1
  import base64
2
- import inspect
3
2
  import pickle
4
3
  import time
5
4
  from typing import Any, Callable, List, Optional
6
5
 
6
+ import dill # Import dill
7
7
  import requests
8
8
 
9
9
  from nebu.containers.container import Container
@@ -47,8 +47,16 @@ def container(
47
47
  )
48
48
  time.sleep(1)
49
49
 
50
- # Get function source code
51
- func_code = inspect.getsource(func)
50
+ # Get function source code using dill
51
+ try:
52
+ func_code = dill.source.getsource(func)
53
+ except (OSError, TypeError) as e:
54
+ raise RuntimeError(
55
+ f"Failed to retrieve source code for function '{func.__name__}'. "
56
+ "This can happen with functions defined dynamically or interactively "
57
+ "(e.g., in a Jupyter notebook or REPL). Ensure the function is defined "
58
+ f"in a standard Python module if possible. Original error: {e}"
59
+ )
52
60
 
53
61
  # Serialize arguments using pickle for complex objects
54
62
  serialized_args = base64.b64encode(pickle.dumps(args)).decode("utf-8")
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  import json
3
3
  import os
4
+ import socket
4
5
  import sys
5
6
  import time
6
7
  import traceback
@@ -8,6 +9,7 @@ from datetime import datetime
8
9
  from typing import Dict, TypeVar
9
10
 
10
11
  import redis
12
+ import socks
11
13
 
12
14
  # Define TypeVar for generic models
13
15
  T = TypeVar("T")
@@ -220,13 +222,24 @@ if not all([REDIS_URL, REDIS_CONSUMER_GROUP, REDIS_STREAM]):
220
222
  print("Missing required Redis environment variables")
221
223
  sys.exit(1)
222
224
 
225
+ # Configure SOCKS proxy before connecting to Redis
226
+ # Use the proxy settings provided by tailscaled
227
+ socks.set_default_proxy(socks.SOCKS5, "localhost", 1055)
228
+ socket.socket = socks.socksocket
229
+ print("Configured SOCKS5 proxy for socket connections via localhost:1055")
230
+
223
231
  # Connect to Redis
224
232
  try:
225
- r = redis.from_url(REDIS_URL)
233
+ # Parse the Redis URL to handle potential credentials or specific DBs if needed
234
+ # Although from_url should work now with the patched socket
235
+ r = redis.from_url(
236
+ REDIS_URL, decode_responses=True
237
+ ) # Added decode_responses for convenience
238
+ r.ping() # Test connection
226
239
  redis_info = REDIS_URL.split("@")[-1] if "@" in REDIS_URL else REDIS_URL
227
- print(f"Connected to Redis at {redis_info}")
240
+ print(f"Connected to Redis via SOCKS proxy at {redis_info}")
228
241
  except Exception as e:
229
- print(f"Failed to connect to Redis: {e}")
242
+ print(f"Failed to connect to Redis via SOCKS proxy: {e}")
230
243
  traceback.print_exc()
231
244
  sys.exit(1)
232
245
 
@@ -1,3 +1,4 @@
1
+ import ast # For parsing notebook code
1
2
  import inspect
2
3
  import re # Import re for fallback check
3
4
  import textwrap
@@ -13,6 +14,7 @@ from typing import (
13
14
  get_type_hints,
14
15
  )
15
16
 
17
+ import dill # Add dill import
16
18
  from pydantic import BaseModel
17
19
 
18
20
  from nebu.containers.models import (
@@ -35,46 +37,301 @@ from .default import DEFAULT_MAX_REPLICAS, DEFAULT_MIN_REPLICAS, DEFAULT_SCALE
35
37
  T = TypeVar("T", bound=BaseModel)
36
38
  R = TypeVar("R", bound=BaseModel)
37
39
 
40
+ # Attribute name for explicitly stored source
41
+ _NEBU_EXPLICIT_SOURCE_ATTR = "_nebu_explicit_source"
38
42
 
39
- def get_model_source(model_class: Any) -> Optional[str]:
40
- """Get the source code of a model class."""
43
+ # --- Jupyter Helper Functions ---
44
+
45
+
46
+ def is_jupyter_notebook():
47
+ """
48
+ Determine if the current code is running inside a Jupyter notebook.
49
+ Returns bool: True if running inside a Jupyter notebook, False otherwise.
50
+ """
51
+ print("[DEBUG Helper] Checking if running in Jupyter...")
52
+ try:
53
+ import IPython
54
+
55
+ ip = IPython.get_ipython()
56
+ if ip is None:
57
+ print("[DEBUG Helper] is_jupyter_notebook: No IPython instance found.")
58
+ return False
59
+ class_name = str(ip.__class__)
60
+ print(f"[DEBUG Helper] is_jupyter_notebook: IPython class name: {class_name}")
61
+ if "ZMQInteractiveShell" in class_name:
62
+ print(
63
+ "[DEBUG Helper] is_jupyter_notebook: Jupyter detected (ZMQInteractiveShell)."
64
+ )
65
+ return True
66
+ print(
67
+ "[DEBUG Helper] is_jupyter_notebook: Not Jupyter (IPython instance found, but not ZMQInteractiveShell)."
68
+ )
69
+ return False
70
+ except Exception as e:
71
+ print(f"[DEBUG Helper] is_jupyter_notebook: Exception occurred: {e}")
72
+ return False
73
+
74
+
75
+ def get_notebook_executed_code():
76
+ """
77
+ Returns all executed code from the current notebook session.
78
+ Returns str or None: All executed code as a string, or None if not possible.
79
+ """
80
+ print("[DEBUG Helper] Attempting to get notebook execution history...")
81
+ try:
82
+ import IPython
83
+
84
+ ip = IPython.get_ipython()
85
+ if ip is None or not hasattr(ip, "history_manager"):
86
+ print(
87
+ "[DEBUG Helper] get_notebook_executed_code: No IPython instance or history_manager."
88
+ )
89
+ return None
90
+ history_manager = ip.history_manager
91
+ # Limiting history range for debugging? Maybe get_tail(N)? For now, get all.
92
+ # history = history_manager.get_range(start=1) # type: ignore
93
+ history = list(history_manager.get_range(start=1)) # type: ignore # Convert to list to get length
94
+ print(
95
+ f"[DEBUG Helper] get_notebook_executed_code: Retrieved {len(history)} history entries."
96
+ )
97
+ source_code = ""
98
+ separator = "\n#<NEBU_CELL_SEP>#\n"
99
+ for _, _, content in history: # Use _ for unused session, lineno
100
+ if isinstance(content, str) and content.strip():
101
+ source_code += content + separator
102
+ print(
103
+ f"[DEBUG Helper] get_notebook_executed_code: Total history source length: {len(source_code)}"
104
+ )
105
+ return source_code
106
+ except Exception as e:
107
+ print(f"[DEBUG Helper] get_notebook_executed_code: Error getting history: {e}")
108
+ return None
109
+
110
+
111
+ def extract_definition_source_from_string(
112
+ source_string: str, def_name: str, def_type: type = ast.FunctionDef
113
+ ) -> Optional[str]:
114
+ """
115
+ Attempts to extract the source code of a function or class from a larger string
116
+ (like notebook history). Finds the *last* complete definition.
117
+ Uses AST parsing for robustness.
118
+ def_type can be ast.FunctionDef or ast.ClassDef.
119
+ """
120
+ print(
121
+ f"[DEBUG Helper] Extracting '{def_name}' ({def_type.__name__}) from history string (len: {len(source_string)})..."
122
+ )
123
+ if not source_string or not def_name:
124
+ print("[DEBUG Helper] extract: Empty source string or def_name.")
125
+ return None
126
+
127
+ cells = source_string.split("#<NEBU_CELL_SEP>#")
128
+ print(f"[DEBUG Helper] extract: Split history into {len(cells)} potential cells.")
129
+ last_found_source = None
130
+
131
+ for i, cell in enumerate(reversed(cells)):
132
+ cell_num = len(cells) - 1 - i
133
+ cell = cell.strip()
134
+ if not cell:
135
+ continue
136
+ # print(f"[DEBUG Helper] extract: Analyzing cell #{cell_num}...") # Can be very verbose
137
+ try:
138
+ tree = ast.parse(cell)
139
+ found_in_cell = False
140
+ for node in ast.walk(tree):
141
+ if (
142
+ isinstance(node, def_type)
143
+ and hasattr(node, "name")
144
+ and node.name == def_name
145
+ ):
146
+ print(
147
+ f"[DEBUG Helper] extract: Found node for '{def_name}' in cell #{cell_num}."
148
+ )
149
+ try:
150
+ # Use ast.get_source_segment for accurate extraction (Python 3.8+)
151
+ func_source = ast.get_source_segment(cell, node)
152
+ if func_source:
153
+ print(
154
+ f"[DEBUG Helper] extract: Successfully extracted source using get_source_segment for '{def_name}'."
155
+ )
156
+ last_found_source = func_source
157
+ found_in_cell = True
158
+ break # Stop searching this cell
159
+ except AttributeError: # Fallback for Python < 3.8
160
+ print(
161
+ f"[DEBUG Helper] extract: get_source_segment failed (likely Py < 3.8), using fallback for '{def_name}'."
162
+ )
163
+ start_lineno = getattr(node, "lineno", 1) - 1
164
+ end_lineno = getattr(node, "end_lineno", start_lineno + 1)
165
+
166
+ if hasattr(node, "decorator_list") and node.decorator_list:
167
+ first_decorator_start_line = (
168
+ getattr(
169
+ node.decorator_list[0], "lineno", start_lineno + 1
170
+ )
171
+ - 1
172
+ ) # type: ignore
173
+ start_lineno = min(start_lineno, first_decorator_start_line)
174
+
175
+ lines = cell.splitlines()
176
+ if 0 <= start_lineno < len(lines) and end_lineno <= len(lines):
177
+ extracted_lines = lines[start_lineno:end_lineno]
178
+ if extracted_lines and (
179
+ extracted_lines[0].strip().startswith("@")
180
+ or extracted_lines[0]
181
+ .strip()
182
+ .startswith(("def ", "class "))
183
+ ):
184
+ last_found_source = "\n".join(extracted_lines)
185
+ print(
186
+ f"[DEBUG Helper] extract: Extracted source via fallback for '{def_name}'."
187
+ )
188
+ found_in_cell = True
189
+ break
190
+ else:
191
+ print(
192
+ f"[DEBUG Helper] extract: Warning: Line numbers out of bounds for {def_name} in cell (fallback)."
193
+ )
194
+
195
+ if found_in_cell:
196
+ print(
197
+ f"[DEBUG Helper] extract: Found and returning source for '{def_name}' from cell #{cell_num}."
198
+ )
199
+ return last_found_source # Found last definition, return immediately
200
+
201
+ except (SyntaxError, ValueError) as e:
202
+ # print(f"[DEBUG Helper] extract: Skipping cell #{cell_num} due to parse error: {e}") # Can be verbose
203
+ continue
204
+ except Exception as e:
205
+ print(
206
+ f"[DEBUG Helper] extract: Warning: AST processing error for {def_name} in cell #{cell_num}: {e}"
207
+ )
208
+ continue
209
+
210
+ if not last_found_source:
211
+ print(
212
+ f"[DEBUG Helper] extract: Definition '{def_name}' of type {def_type.__name__} not found in history search."
213
+ )
214
+ return last_found_source
215
+
216
+
217
+ # --- End Jupyter Helper Functions ---
218
+
219
+
220
+ def include(obj: Any) -> Any:
221
+ """
222
+ Decorator to explicitly capture the source code of a function or class,
223
+ intended for use in environments where inspect/dill might fail (e.g., Jupyter).
224
+ """
225
+ try:
226
+ source = dill.source.getsource(obj)
227
+ dedented_source = textwrap.dedent(source)
228
+ setattr(obj, _NEBU_EXPLICIT_SOURCE_ATTR, dedented_source)
229
+ print(
230
+ f"[DEBUG @include] Successfully captured source for: {getattr(obj, '__name__', str(obj))}"
231
+ )
232
+ except Exception as e:
233
+ # Don't fail the definition, just warn
234
+ print(
235
+ f"Warning: @include could not capture source for {getattr(obj, '__name__', str(obj))}: {e}. Automatic source retrieval will be attempted later."
236
+ )
237
+ return obj
238
+
239
+
240
+ def get_model_source(
241
+ model_class: Any, notebook_code: Optional[str] = None
242
+ ) -> Optional[str]:
243
+ """
244
+ Get the source code of a model class.
245
+ Checks explicit source, then notebook history (if provided), then dill.
246
+ """
247
+ model_name_str = getattr(model_class, "__name__", str(model_class))
248
+ print(f"[DEBUG get_model_source] Getting source for: {model_name_str}")
249
+ # 1. Check explicit source
250
+ explicit_source = getattr(model_class, _NEBU_EXPLICIT_SOURCE_ATTR, None)
251
+ if explicit_source:
252
+ print(
253
+ f"[DEBUG get_model_source] Using explicit source (@include) for: {model_name_str}"
254
+ )
255
+ return explicit_source
256
+
257
+ # 2. Check notebook history
258
+ if notebook_code and hasattr(model_class, "__name__"):
259
+ print(
260
+ f"[DEBUG get_model_source] Attempting notebook history extraction for: {model_class.__name__}"
261
+ )
262
+ extracted_source = extract_definition_source_from_string(
263
+ notebook_code, model_class.__name__, ast.ClassDef
264
+ )
265
+ if extracted_source:
266
+ print(
267
+ f"[DEBUG get_model_source] Using notebook history source for: {model_class.__name__}"
268
+ )
269
+ return extracted_source
270
+ else:
271
+ print(
272
+ f"[DEBUG get_model_source] Notebook history extraction failed for: {model_class.__name__}. Proceeding to dill."
273
+ )
274
+
275
+ # 3. Fallback to dill
41
276
  try:
42
- source = inspect.getsource(model_class)
277
+ print(
278
+ f"[DEBUG get_model_source] Attempting dill fallback for: {model_name_str}"
279
+ )
280
+ source = dill.source.getsource(model_class)
281
+ print(f"[DEBUG get_model_source] Using dill source for: {model_name_str}")
43
282
  return textwrap.dedent(source)
44
- except (IOError, TypeError):
45
- print(f"[DEBUG get_model_source] Failed for: {model_class}") # Added debug
283
+ except (IOError, TypeError, OSError) as e:
284
+ print(
285
+ f"[DEBUG get_model_source] Failed dill fallback for: {model_name_str}: {e}"
286
+ )
46
287
  return None
47
288
 
48
289
 
49
- def get_type_source(type_obj: Any) -> Optional[Any]:
290
+ # Reintroduce get_type_source to handle generics
291
+ def get_type_source(
292
+ type_obj: Any, notebook_code: Optional[str] = None
293
+ ) -> Optional[Any]:
50
294
  """Get the source code for a type, including generic parameters."""
51
- # If it's a class, get its source
52
- if isinstance(type_obj, type):
53
- return get_model_source(type_obj)
54
-
55
- # If it's a GenericAlias (like V1StreamMessage[SomeType])
56
- # Use get_origin and get_args for robustness
295
+ type_obj_str = str(type_obj)
296
+ print(f"[DEBUG get_type_source] Getting source for type: {type_obj_str}")
57
297
  origin = get_origin(type_obj)
58
298
  args = get_args(type_obj)
59
299
 
60
300
  if origin is not None:
61
- origin_source = get_model_source(origin)
301
+ # Use updated get_model_source for origin
302
+ print(
303
+ f"[DEBUG get_type_source] Detected generic type. Origin: {origin}, Args: {args}"
304
+ )
305
+ origin_source = get_model_source(origin, notebook_code)
62
306
  args_sources = []
63
307
 
64
- # Get sources for all type arguments
308
+ # Recursively get sources for all type arguments
65
309
  for arg in args:
66
- arg_source = get_type_source(arg)
310
+ print(
311
+ f"[DEBUG get_type_source] Recursively getting source for generic arg #{arg}"
312
+ )
313
+ arg_source = get_type_source(arg, notebook_code)
67
314
  if arg_source:
68
315
  args_sources.append(arg_source)
69
316
 
70
- # Return tuple only if origin source and some arg sources were found
317
+ # Return tuple only if origin source or some arg sources were found
71
318
  if origin_source or args_sources:
72
- return (
73
- origin_source,
74
- args_sources,
75
- ) # Return even if origin_source is None if args_sources exist
319
+ print(
320
+ f"[DEBUG get_type_source] Returning tuple source for generic: {type_obj_str}"
321
+ )
322
+ return (origin_source, args_sources)
323
+
324
+ # Fallback if not a class or recognizable generic alias
325
+ # Try get_model_source as a last resort for unknown types
326
+ fallback_source = get_model_source(type_obj, notebook_code)
327
+ if fallback_source:
328
+ print(
329
+ f"[DEBUG get_type_source] Using fallback get_model_source for: {type_obj_str}"
330
+ )
331
+ return fallback_source
76
332
 
77
- return None # Fallback if not a class or recognizable generic alias
333
+ print(f"[DEBUG get_type_source] Failed to get source for: {type_obj_str}")
334
+ return None
78
335
 
79
336
 
80
337
  def processor(
@@ -96,373 +353,434 @@ def processor(
96
353
  no_delete: bool = False,
97
354
  include: Optional[List[Any]] = None,
98
355
  ):
99
- """
100
- Decorator that converts a function into a Processor.
101
-
102
- Args:
103
- image: The container image to use for the processor
104
- setup_script: Optional setup script to run before starting the processor
105
- scale: Optional scaling configuration
106
- min_replicas: Minimum number of replicas to maintain
107
- max_replicas: Maximum number of replicas to scale to
108
- platform: Optional compute platform to run on
109
- accelerators: Optional list of accelerator types
110
- namespace: Optional namespace for the processor
111
- labels: Optional labels to apply to the processor
112
- env: Optional environment variables
113
- volumes: Optional volume mounts
114
- resources: Optional resource requirements
115
- meters: Optional metering configuration
116
- authz: Optional authorization configuration
117
- python_cmd: Optional python command to use
118
- no_delete: Whether to prevent deleting the processor on updates
119
- include: Optional list of Python objects whose source code should be included
120
- """
121
-
122
356
  def decorator(
123
357
  func: Callable[[Any], Any],
124
- ) -> Processor: # Changed T/R to Any for broader compatibility
125
- # Prepare environment variables early
358
+ ) -> Processor:
359
+ # Moved init print here
360
+ print(
361
+ f"[DEBUG Decorator Init] @processor decorating function '{func.__name__}'"
362
+ )
126
363
  all_env = env or []
364
+ processor_name = func.__name__
127
365
 
128
- # --- Process Included Objects First ---
129
- included_sources: Dict[Any, Any] = {} # Store source keyed by the object itself
366
+ # --- Determine Environment and Get Notebook Code ---
367
+ print("[DEBUG Decorator] Determining execution environment...")
368
+ in_jupyter = is_jupyter_notebook()
369
+ notebook_code = None
370
+ if in_jupyter:
371
+ print("[DEBUG Decorator] Jupyter environment detected.")
372
+ notebook_code = get_notebook_executed_code()
373
+ if not notebook_code:
374
+ print(
375
+ "[DEBUG Decorator] Warning: Failed to get Jupyter execution history. Will attempt dill."
376
+ )
377
+ else:
378
+ print(
379
+ f"[DEBUG Decorator] Retrieved notebook history (length: {len(notebook_code)})."
380
+ )
381
+ else:
382
+ print("[DEBUG Decorator] Non-Jupyter environment detected.")
383
+ # --- End Environment Determination ---
384
+
385
+ # --- Process Manually Included Objects ---
386
+ included_sources: Dict[Any, Any] = {}
130
387
  if include:
131
- print(f"[DEBUG Decorator] Processing included objects: {include}")
388
+ print(f"[DEBUG Decorator] Processing manually included objects: {include}")
132
389
  for i, obj in enumerate(include):
133
- # Directly use get_model_source as include expects types/classes usually
134
- obj_source = get_model_source(obj)
390
+ obj_name_str = getattr(obj, "__name__", str(obj))
391
+ print(
392
+ f"[DEBUG Decorator] Getting source for manually included object: {obj_name_str}"
393
+ )
394
+ obj_source = get_model_source(obj, notebook_code)
135
395
  if obj_source:
136
- print(f"[DEBUG Decorator] Found source for included object: {obj}")
137
- included_sources[obj] = obj_source # Store source by object
138
- # Add to env vars immediately (simplifies later logic)
396
+ included_sources[obj] = obj_source
139
397
  env_key = f"INCLUDED_OBJECT_{i}_SOURCE"
140
398
  all_env.append(V1EnvVar(key=env_key, value=obj_source))
141
- print(f"[DEBUG Decorator] Added {env_key} for {obj}")
142
-
399
+ print(
400
+ f"[DEBUG Decorator] Added source to env for included obj: {obj_name_str}"
401
+ )
143
402
  else:
144
- # Optionally raise an error or log a warning if source can't be found
145
403
  print(
146
- f"Warning: Could not retrieve source via get_model_source for included object: {obj}. Decorator might fail if this type is needed but cannot be auto-detected."
404
+ f"Warning: Could not retrieve source for manually included object: {obj_name_str}"
147
405
  )
148
406
  print(
149
407
  f"[DEBUG Decorator] Finished processing included objects. Sources found: {len(included_sources)}"
150
408
  )
151
- # --- End Included Objects Processing ---
409
+ else:
410
+ print("[DEBUG Decorator] No manually included objects specified.")
411
+ # --- End Manually Included Objects ---
152
412
 
153
- # Validate function signature
413
+ # --- Validate Function Signature and Types ---
414
+ print(
415
+ f"[DEBUG Decorator] Validating signature and type hints for {processor_name}..."
416
+ )
154
417
  sig = inspect.signature(func)
155
418
  params = list(sig.parameters.values())
156
-
157
419
  if len(params) != 1:
158
- raise TypeError(f"Function {func.__name__} must take exactly one parameter")
420
+ raise TypeError(f"{processor_name} must take one parameter")
159
421
 
160
- # Check parameter type hint
161
422
  try:
162
- # Use eval_str=True for forward references if needed, requires Python 3.10+ globals/locals
163
- type_hints = get_type_hints(
164
- func, globalns=func.__globals__, localns=None
165
- ) # Pass globals
423
+ type_hints = get_type_hints(func, globalns=func.__globals__, localns=None)
424
+ print(f"[DEBUG Decorator] Raw type hints: {type_hints}")
166
425
  except Exception as e:
167
- print(
168
- f"[DEBUG Decorator] Error getting type hints for {func.__name__}: {e}"
169
- )
426
+ print(f"[DEBUG Decorator] Error getting type hints: {e}")
170
427
  raise TypeError(
171
- f"Could not evaluate type hints for {func.__name__}. Ensure all types are defined or imported."
428
+ f"Could not evaluate type hints for {processor_name}: {e}"
172
429
  ) from e
173
430
 
174
431
  param_name = params[0].name
175
432
  if param_name not in type_hints:
176
433
  raise TypeError(
177
- f"Parameter {param_name} in function {func.__name__} must have a type annotation"
434
+ f"{processor_name} parameter '{param_name}' must have type hint"
178
435
  )
179
436
  param_type = type_hints[param_name]
437
+ param_type_str_repr = str(param_type) # Use string for regex
438
+ print(
439
+ f"[DEBUG Decorator] Parameter '{param_name}' type hint: {param_type_str_repr}"
440
+ )
180
441
 
181
- # --- Determine Input Type, Content Type, and is_stream_message ---
182
- print(f"[DEBUG Decorator] Full type_hints: {type_hints}")
183
- print(f"[DEBUG Decorator] Detected param_type: {param_type}")
442
+ if "return" not in type_hints:
443
+ raise TypeError(f"{processor_name} must have return type hint")
444
+ return_type = type_hints["return"]
445
+ print(f"[DEBUG Decorator] Return type hint: {return_type}")
446
+
447
+ # --- Determine Input Type (StreamMessage, ContentType) ---
448
+ print(
449
+ f"[DEBUG Decorator] Determining input type structure for param type hint: {param_type_str_repr}"
450
+ )
184
451
  origin = get_origin(param_type)
185
452
  args = get_args(param_type)
186
- print(f"[DEBUG Decorator] Param type origin (using get_origin): {origin}")
187
- print(f"[DEBUG Decorator] Param type args (using get_args): {args}")
188
- if origin:
189
- print(
190
- f"[DEBUG Decorator] Origin name: {getattr(origin, '__name__', 'N/A')}, module: {getattr(origin, '__module__', 'N/A')}"
191
- )
192
- print(
193
- f"[DEBUG Decorator] V1StreamMessage name: {V1StreamMessage.__name__}, module: {V1StreamMessage.__module__}"
194
- )
195
-
453
+ print(f"[DEBUG Decorator] get_origin result: {origin}, get_args result: {args}")
196
454
  is_stream_message = False
197
455
  content_type = None
198
456
 
199
- # Check 1: Standard check using get_origin
200
- if (
457
+ # Check 1: Standard introspection
458
+ if origin is V1StreamMessage or (
201
459
  origin is not None
202
460
  and origin.__name__ == V1StreamMessage.__name__
203
461
  and origin.__module__ == V1StreamMessage.__module__
204
462
  ):
463
+ print(
464
+ "[DEBUG Decorator] Input type identified as V1StreamMessage via get_origin."
465
+ )
205
466
  is_stream_message = True
206
- print("[DEBUG Decorator] V1StreamMessage detected via origin check.")
207
467
  if args:
208
468
  content_type = args[0]
209
-
210
- # Check 2: Fallback check using string representation
469
+ print(
470
+ f"[DEBUG Decorator] Content type extracted via get_args: {content_type}"
471
+ )
472
+ else:
473
+ print(
474
+ "[DEBUG Decorator] V1StreamMessage detected, but no generic arguments found via get_args."
475
+ )
476
+ # Check 2: Direct type check
477
+ elif isinstance(param_type, type) and param_type is V1StreamMessage:
478
+ print(
479
+ "[DEBUG Decorator] Input type identified as direct V1StreamMessage type."
480
+ )
481
+ is_stream_message = True
482
+ # Check 3: Regex fallback on string representation
211
483
  elif origin is None:
212
- type_str = str(param_type)
213
- match = re.match(
214
- r"<class 'nebu\.processors\.models\.V1StreamMessage\[(.*)\]\'>",
215
- type_str,
484
+ print(
485
+ f"[DEBUG Decorator] get_origin failed. Attempting regex fallback on type string: '{param_type_str_repr}'"
216
486
  )
217
- if match:
487
+ # Use param_type_str_repr in match calls
488
+ generic_match = re.match(
489
+ r"^<class '(?:[a-zA-Z0-9_.]+\.)?V1StreamMessage\[(.+?)\]'>$",
490
+ param_type_str_repr,
491
+ )
492
+ if generic_match:
218
493
  print(
219
- "[DEBUG Decorator] V1StreamMessage detected via string regex check (origin/args failed)."
494
+ "[DEBUG Decorator] Regex matched generic V1StreamMessage pattern!"
220
495
  )
221
- content_type_name = match.group(1)
496
+ is_stream_message = True
497
+ content_type_name_str = generic_match.group(1).strip()
222
498
  print(
223
- f"[DEBUG Decorator] Manually parsed content_type name: {content_type_name}"
499
+ f"[DEBUG Decorator] Captured content type name via regex: '{content_type_name_str}'"
224
500
  )
225
- # Attempt to find the type
226
- resolved_type = None
227
- func_globals = func.__globals__
228
- if content_type_name in func_globals:
229
- resolved_type = func_globals[content_type_name]
501
+ try:
502
+ resolved_type = eval(content_type_name_str, func.__globals__)
503
+ content_type = resolved_type
230
504
  print(
231
- f"[DEBUG Decorator] Found content type '{content_type_name}' in function globals."
505
+ f"[DEBUG Decorator] Successfully resolved content type name '{content_type_name_str}' to type: {content_type}"
232
506
  )
233
- else:
234
- func_module = inspect.getmodule(func)
235
- if func_module and hasattr(func_module, content_type_name):
236
- resolved_type = getattr(func_module, content_type_name)
237
- print(
238
- f"[DEBUG Decorator] Found content type '{content_type_name}' in function module."
239
- )
240
-
241
- if resolved_type:
242
- content_type = resolved_type
243
- is_stream_message = True # Set flag *only if* resolved
507
+ except NameError:
508
+ print(
509
+ f"[DEBUG Decorator] Warning: Regex found content type name '{content_type_name_str}', but it's not defined in function's globals. Consumer might fail."
510
+ )
511
+ content_type = None
512
+ except Exception as e:
513
+ print(
514
+ f"[DEBUG Decorator] Warning: Error evaluating content type name '{content_type_name_str}': {e}"
515
+ )
516
+ content_type = None
517
+ else:
518
+ # Use param_type_str_repr in match calls
519
+ simple_match = re.match(
520
+ r"^<class '(?:[a-zA-Z0-9_.]+\.)?V1StreamMessage'>$",
521
+ param_type_str_repr,
522
+ )
523
+ if simple_match:
524
+ print(
525
+ "[DEBUG Decorator] Regex identified direct V1StreamMessage (no generic) from string."
526
+ )
527
+ is_stream_message = True
244
528
  else:
245
529
  print(
246
- f"[DEBUG Decorator] Fallback failed: Could not find type '{content_type_name}' in globals or module. Use 'include'."
530
+ f"[DEBUG Decorator] Regex did not match V1StreamMessage pattern for string '{param_type_str_repr}'. Assuming not StreamMessage."
247
531
  )
248
- # else: Fallback regex did not match
249
-
250
- # Check 3: Handle direct V1StreamMessage
251
- elif param_type is V1StreamMessage:
252
- print("[DEBUG Decorator] V1StreamMessage detected via direct type check.")
253
- is_stream_message = True
254
- # content_type remains None
532
+ else:
533
+ print(
534
+ f"[DEBUG Decorator] Input parameter '{param_name}' type ({param_type_str_repr}) identified as non-StreamMessage type (origin was not None and not V1StreamMessage)."
535
+ )
255
536
 
256
- print(f"[DEBUG Decorator] Final is_stream_message: {is_stream_message}")
257
- print(f"[DEBUG Decorator] Final content_type: {content_type}")
537
+ print(
538
+ f"[DEBUG Decorator] Final Input Type Determination: is_stream_message={is_stream_message}, content_type={content_type}"
539
+ )
258
540
  # --- End Input Type Determination ---
259
541
 
260
- # --- Validate Parameter Type is BaseModel ---
261
- type_to_check_for_basemodel = None
262
- if is_stream_message:
263
- if content_type:
264
- type_to_check_for_basemodel = content_type
265
- # else: Base V1StreamMessage itself is a BaseModel, no need to check further
266
- else:
267
- type_to_check_for_basemodel = param_type
542
+ # --- Validate Types are BaseModel ---
543
+ print(
544
+ "[DEBUG Decorator] Validating parameter and return types are BaseModel subclasses..."
545
+ )
268
546
 
269
- if type_to_check_for_basemodel:
270
- actual_type_to_check = (
271
- get_origin(type_to_check_for_basemodel) or type_to_check_for_basemodel
547
+ def check_basemodel(type_to_check, desc):
548
+ print(
549
+ f"[DEBUG Decorator] check_basemodel: Checking {desc} - Type: {type_to_check}"
550
+ )
551
+ if not type_to_check:
552
+ print(
553
+ f"[DEBUG Decorator] check_basemodel: Skipping check for {desc} (type is None/empty)."
554
+ )
555
+ return
556
+ actual_type = get_origin(type_to_check) or type_to_check
557
+ print(
558
+ f"[DEBUG Decorator] check_basemodel: Actual type for {desc}: {actual_type}"
272
559
  )
273
- if not issubclass(actual_type_to_check, BaseModel):
560
+ if isinstance(actual_type, type) and not issubclass(actual_type, BaseModel):
561
+ print(
562
+ f"[DEBUG Decorator] check_basemodel: Error - {desc} effective type ({actual_type.__name__}) is not a BaseModel subclass."
563
+ )
274
564
  raise TypeError(
275
- f"Parameter '{param_name}' effective type ({actual_type_to_check.__name__}) in function '{func.__name__}' must be a BaseModel subclass"
565
+ f"{desc} effective type ({actual_type.__name__}) must be BaseModel subclass"
566
+ )
567
+ elif not isinstance(actual_type, type):
568
+ print(
569
+ f"[DEBUG Decorator] check_basemodel: Warning - {desc} effective type '{actual_type}' is not a class. Cannot verify BaseModel subclass."
570
+ )
571
+ else:
572
+ print(
573
+ f"[DEBUG Decorator] check_basemodel: OK - {desc} effective type ({actual_type.__name__}) is a BaseModel subclass."
276
574
  )
277
- # --- End Parameter Validation ---
278
575
 
279
- # --- Validate Return Type ---
280
- if "return" not in type_hints:
281
- raise TypeError(
282
- f"Function {func.__name__} must have a return type annotation"
283
- )
284
- return_type = type_hints["return"]
285
- actual_return_type = get_origin(return_type) or return_type
286
- if not issubclass(actual_return_type, BaseModel):
287
- raise TypeError(
288
- f"Return value of function {func.__name__} must be a BaseModel subclass"
289
- )
290
- # --- End Return Type Validation ---
576
+ effective_param_type = (
577
+ content_type
578
+ if is_stream_message and content_type
579
+ else param_type
580
+ if not is_stream_message
581
+ else None
582
+ )
583
+ check_basemodel(effective_param_type, f"Parameter '{param_name}'")
584
+ check_basemodel(return_type, "Return value")
585
+ print("[DEBUG Decorator] Type validation complete.")
586
+ # --- End Type Validation ---
291
587
 
292
588
  # --- Get Function Source ---
293
- processor_name = func.__name__
294
- try:
295
- raw_function_source = inspect.getsource(func)
296
- # ... (rest of source processing remains the same) ...
297
- lines = raw_function_source.splitlines()
298
- func_def_index = -1
299
- decorator_lines = 0
300
- in_decorator = False
301
- for i, line in enumerate(lines):
302
- stripped_line = line.strip()
303
- if stripped_line.startswith("@"):
304
- in_decorator = True
305
- decorator_lines += 1
306
- continue # Skip decorator line
307
- if in_decorator and stripped_line.endswith(
308
- ")"
309
- ): # Simple check for end of decorator args
310
- in_decorator = False
311
- decorator_lines += 1
312
- continue
313
- if in_decorator:
314
- decorator_lines += 1
315
- continue # Skip multi-line decorator args
316
-
317
- if stripped_line.startswith("def "):
318
- func_def_index = i
319
- break
320
-
321
- if func_def_index != -1:
322
- # Keep lines from the 'def' line onwards
323
- function_source = "\n".join(lines[func_def_index:])
589
+ print(
590
+ f"[DEBUG Decorator] Getting source code for function '{processor_name}'..."
591
+ )
592
+ function_source = None
593
+ explicit_source = getattr(func, _NEBU_EXPLICIT_SOURCE_ATTR, None)
594
+
595
+ if explicit_source:
596
+ print(
597
+ f"[DEBUG Decorator] Using explicit source (@include) for function {processor_name}"
598
+ )
599
+ function_source = explicit_source
600
+ elif in_jupyter and notebook_code:
601
+ print(
602
+ f"[DEBUG Decorator] Attempting notebook history extraction for function '{processor_name}'..."
603
+ )
604
+ function_source = extract_definition_source_from_string(
605
+ notebook_code, processor_name, ast.FunctionDef
606
+ )
607
+ if function_source:
608
+ print(
609
+ f"[DEBUG Decorator] Found function '{processor_name}' source in notebook history."
610
+ )
324
611
  else:
325
- raise ValueError(
326
- f"Could not find function definition 'def' in source for {func.__name__}"
612
+ print(
613
+ f"[DEBUG Decorator] Failed to find function '{processor_name}' in notebook history, falling back to dill."
327
614
  )
328
-
615
+ if function_source is None:
329
616
  print(
330
- f"[DEBUG Decorator] Processed function source for {func.__name__}:\n{function_source[:200]}..."
617
+ f"[DEBUG Decorator] Using dill fallback for function '{processor_name}'..."
331
618
  )
619
+ try:
620
+ raw_function_source = dill.source.getsource(func)
621
+ function_source = textwrap.dedent(raw_function_source)
622
+ print(
623
+ f"[DEBUG Decorator] Successfully got source via dill for '{processor_name}'."
624
+ )
625
+ except (IOError, TypeError, OSError) as e:
626
+ print(
627
+ f"[DEBUG Decorator] Dill fallback failed for '{processor_name}': {e}"
628
+ )
629
+ if not (in_jupyter and notebook_code):
630
+ raise ValueError(
631
+ f"Could not retrieve source for '{processor_name}' using dill: {e}"
632
+ ) from e
332
633
 
333
- except (IOError, TypeError) as e:
334
- print(f"[DEBUG Decorator] Error getting source for {func.__name__}: {e}")
634
+ if function_source is None: # Final check after all attempts
335
635
  raise ValueError(
336
- f"Could not retrieve source code for function {func.__name__}: {e}"
337
- ) from e
636
+ f"Failed to obtain source code for function '{processor_name}' using any method."
637
+ )
638
+
639
+ print(f"[DEBUG Decorator] Final function source obtained for '{processor_name}' (len: {len(function_source)}). Source starts:\n-------\
640
+ {function_source[:250]}...\n-------")
338
641
  # --- End Function Source ---
339
642
 
340
- # --- Get Model Sources (Prioritizing Included) ---
643
+ # --- Get Model Sources ---
644
+ print("[DEBUG Decorator] Getting model sources...")
341
645
  input_model_source = None
342
646
  output_model_source = None
343
647
  content_type_source = None
344
- stream_message_source = get_model_source(V1StreamMessage) # Still get this
648
+ print("[DEBUG Decorator] Getting base V1StreamMessage source...")
649
+ stream_message_source = get_type_source(V1StreamMessage, notebook_code)
345
650
 
346
- # Get content_type source (if applicable)
347
- if is_stream_message and content_type:
348
- if content_type in included_sources:
349
- content_type_source = included_sources[content_type]
350
- print(
351
- f"[DEBUG Decorator] Using included source for content_type: {content_type}"
352
- )
353
- else:
651
+ if is_stream_message:
652
+ print(
653
+ f"[DEBUG Decorator] Input is StreamMessage. Content type: {content_type}"
654
+ )
655
+ if content_type:
354
656
  print(
355
- f"[DEBUG Decorator] Attempting get_type_source for content_type: {content_type}"
657
+ f"[DEBUG Decorator] Getting source for content_type: {content_type}"
356
658
  )
357
- content_type_source = get_type_source(content_type)
659
+ content_type_source = get_type_source(content_type, notebook_code)
358
660
  if content_type_source is None:
359
661
  print(
360
- f"[DEBUG Decorator] Warning: get_type_source failed for content_type: {content_type}. Consumer might fail if not included."
662
+ f"Warning: Failed to get source for content_type: {content_type}"
361
663
  )
362
-
363
- print(
364
- f"[DEBUG Decorator] Final content_type_source: {str(content_type_source)[:100]}..."
365
- )
366
-
367
- # Get input_model source (which is V1StreamMessage if is_stream_message)
368
- if is_stream_message:
369
- input_model_source = (
370
- stream_message_source # Always use base stream message source
371
- )
372
- elif (
373
- param_type in included_sources
374
- ): # Check if non-stream-message input type was included
375
- input_model_source = included_sources[param_type]
664
+ else: # Not a stream message
376
665
  print(
377
- f"[DEBUG Decorator] Using included source for param_type: {param_type}"
666
+ f"[DEBUG Decorator] Input is not StreamMessage. Getting source for param_type: {param_type}"
378
667
  )
379
- else: # Fallback for non-stream-message, non-included input type
380
- print(
381
- f"[DEBUG Decorator] Attempting get_type_source for param_type: {param_type}"
382
- )
383
- input_model_source = get_type_source(param_type)
668
+ input_model_source = get_type_source(param_type, notebook_code)
384
669
  if input_model_source is None:
385
670
  print(
386
- f"[DEBUG Decorator] Warning: get_type_source failed for param_type: {param_type}. Consumer might fail if not included."
671
+ f"Warning: Failed to get source for input param_type: {param_type}"
387
672
  )
673
+
674
+ print(f"[DEBUG Decorator] Getting source for return_type: {return_type}")
675
+ output_model_source = get_type_source(return_type, notebook_code)
676
+ if output_model_source is None:
677
+ print(f"Warning: Failed to get source for return_type: {return_type}")
678
+
388
679
  print(
389
- f"[DEBUG Decorator] Final input_model_source: {str(input_model_source)[:100]}..."
680
+ f"[DEBUG Decorator] Source Result - Content Type: {'Found' if content_type_source else 'Not Found or N/A'}"
681
+ )
682
+ print(
683
+ f"[DEBUG Decorator] Source Result - Input Model (non-stream): {'Found' if input_model_source else 'Not Found or N/A'}"
684
+ )
685
+ print(
686
+ f"[DEBUG Decorator] Source Result - Output Model: {'Found' if output_model_source else 'Not Found'}"
390
687
  )
391
-
392
- # Get output_model source
393
- if return_type in included_sources:
394
- output_model_source = included_sources[return_type]
395
- print(
396
- f"[DEBUG Decorator] Using included source for return_type: {return_type}"
397
- )
398
- else:
399
- print(
400
- f"[DEBUG Decorator] Attempting get_type_source for return_type: {return_type}"
401
- )
402
- output_model_source = get_type_source(return_type)
403
- if output_model_source is None:
404
- print(
405
- f"[DEBUG Decorator] Warning: get_type_source failed for return_type: {return_type}. Consumer might fail if not included."
406
- )
407
688
  print(
408
- f"[DEBUG Decorator] Final output_model_source: {str(output_model_source)[:100]}..."
689
+ f"[DEBUG Decorator] Source Result - Base StreamMessage: {'Found' if stream_message_source else 'Not Found'}"
409
690
  )
410
691
  # --- End Model Sources ---
411
692
 
412
693
  # --- Populate Environment Variables ---
413
694
  print("[DEBUG Decorator] Populating environment variables...")
414
695
  all_env.append(V1EnvVar(key="FUNCTION_SOURCE", value=function_source))
415
- all_env.append(V1EnvVar(key="FUNCTION_NAME", value=func.__name__))
696
+ all_env.append(V1EnvVar(key="FUNCTION_NAME", value=processor_name))
416
697
 
417
- # Add model source codes (handle tuples from get_type_source if necessary, although unlikely with prioritization)
418
698
  def add_source_to_env(key_base: str, source: Any):
419
- if source:
420
- if isinstance(source, tuple):
421
- # This path is less likely now with include prioritization
422
- if source[0]: # Origin source
699
+ print(f"[DEBUG Decorator] add_source_to_env: Processing key '{key_base}'")
700
+ if not source:
701
+ print(
702
+ f"[DEBUG Decorator] add_source_to_env: No source for '{key_base}', skipping."
703
+ )
704
+ return
705
+
706
+ if isinstance(source, tuple):
707
+ origin_src, arg_srcs = source
708
+ print(
709
+ f"[DEBUG Decorator] add_source_to_env: '{key_base}' is tuple source. Origin found: {bool(origin_src)}, Num args: {len(arg_srcs)}"
710
+ )
711
+ if origin_src and isinstance(origin_src, str):
712
+ all_env.append(V1EnvVar(key=f"{key_base}_SOURCE", value=origin_src))
713
+ print(f"[DEBUG Decorator] Added env var {key_base}_SOURCE (origin)")
714
+ for i, arg_src in enumerate(arg_srcs):
715
+ if isinstance(arg_src, str):
423
716
  all_env.append(
424
- V1EnvVar(key=f"{key_base}_SOURCE", value=source[0])
717
+ V1EnvVar(key=f"{key_base}_ARG_{i}_SOURCE", value=arg_src)
425
718
  )
426
- for i, arg_source in enumerate(source[1]): # Arg sources
427
- all_env.append(
428
- V1EnvVar(key=f"{key_base}_ARG_{i}_SOURCE", value=arg_source)
719
+ print(
720
+ f"[DEBUG Decorator] Added env var {key_base}_ARG_{i}_SOURCE"
429
721
  )
430
- else: # Simple string source
431
- all_env.append(V1EnvVar(key=f"{key_base}_SOURCE", value=source))
722
+ elif isinstance(arg_src, tuple):
723
+ arg_origin_src, _ = arg_src
724
+ if arg_origin_src and isinstance(arg_origin_src, str):
725
+ all_env.append(
726
+ V1EnvVar(
727
+ key=f"{key_base}_ARG_{i}_SOURCE",
728
+ value=arg_origin_src,
729
+ )
730
+ )
731
+ print(
732
+ f"[DEBUG Decorator] Added env var {key_base}_ARG_{i}_SOURCE (nested origin)"
733
+ )
734
+ else:
735
+ print(
736
+ f"[DEBUG Decorator] Skipping complex/non-string nested arg origin for {key_base}_ARG_{i}"
737
+ )
738
+ else:
739
+ print(
740
+ f"[DEBUG Decorator] Skipping complex/non-string arg source for {key_base}_ARG_{i}"
741
+ )
742
+ elif isinstance(source, str):
743
+ all_env.append(V1EnvVar(key=f"{key_base}_SOURCE", value=source))
744
+ print(f"[DEBUG Decorator] Added env var {key_base}_SOURCE (string)")
745
+ else:
746
+ print(
747
+ f"[DEBUG Decorator] Warning: Unknown source type for {key_base}: {type(source)}. Skipping."
748
+ )
432
749
 
433
750
  add_source_to_env("INPUT_MODEL", input_model_source)
434
751
  add_source_to_env("OUTPUT_MODEL", output_model_source)
435
752
  add_source_to_env("CONTENT_TYPE", content_type_source)
436
- add_source_to_env(
437
- "STREAM_MESSAGE", stream_message_source
438
- ) # Add base stream message source
439
-
440
- # Type names for consumer validation/parsing
441
- all_env.append(
442
- V1EnvVar(
443
- key="PARAM_TYPE_STR", value=str(param_type)
444
- ) # Send string representation
445
- )
446
- all_env.append(
447
- V1EnvVar(
448
- key="RETURN_TYPE_STR", value=str(return_type)
449
- ) # Send string representation
450
- )
753
+ add_source_to_env("STREAM_MESSAGE", stream_message_source)
754
+
755
+ print("[DEBUG Decorator] Adding type info env vars...")
756
+ all_env.append(V1EnvVar(key="PARAM_TYPE_STR", value=param_type_str_repr))
757
+ all_env.append(V1EnvVar(key="RETURN_TYPE_STR", value=str(return_type)))
451
758
  all_env.append(V1EnvVar(key="IS_STREAM_MESSAGE", value=str(is_stream_message)))
452
- if content_type:
759
+ if content_type and hasattr(content_type, "__name__"):
453
760
  all_env.append(
454
761
  V1EnvVar(key="CONTENT_TYPE_NAME", value=content_type.__name__)
455
762
  )
456
-
457
763
  all_env.append(V1EnvVar(key="MODULE_NAME", value=func.__module__))
764
+ print("[DEBUG Decorator] Finished populating environment variables.")
458
765
  # --- End Environment Variables ---
459
766
 
460
767
  # --- Final Setup ---
768
+ print("[DEBUG Decorator] Preparing final Processor object...")
461
769
  metadata = V1ResourceMetaRequest(
462
770
  name=processor_name, namespace=namespace, labels=labels
463
771
  )
464
772
  consumer_command = f"{python_cmd} -m nebu.processors.consumer"
465
- final_command = f"{python_cmd} -m pip install redis nebu\n\n{setup_script or ''}\n\n{consumer_command}"
773
+ setup_commands = [
774
+ f"{python_cmd} -m pip install dill pydantic redis nebu", # Base deps
775
+ ]
776
+ if setup_script:
777
+ print("[DEBUG Decorator] Adding setup script to command.")
778
+ setup_commands.append(f"\n{setup_script}\n")
779
+ setup_commands.append(consumer_command)
780
+ final_command = "\n".join(setup_commands)
781
+ print(
782
+ f"[DEBUG Decorator] Final container command:\n-------\n{final_command}\n-------"
783
+ )
466
784
 
467
785
  container_request = V1ContainerRequest(
468
786
  image=image,
@@ -477,15 +795,16 @@ def processor(
477
795
  platform=platform,
478
796
  metadata=metadata,
479
797
  )
480
- print("[DEBUG Decorator] Final Container Request Env Vars:")
798
+ print("[DEBUG Decorator] Final Container Request Env Vars (Summary):")
481
799
  for env_var in all_env:
482
- print(
483
- f"[DEBUG Decorator] {env_var.key}: {str(env_var.value)[:70]}..."
484
- ) # Print key and start of value
800
+ if "SOURCE" in env_var.key:
801
+ print(f"[DEBUG Decorator] {env_var.key}: <source code present>")
802
+ else:
803
+ print(f"[DEBUG Decorator] {env_var.key}: {env_var.value}")
485
804
 
486
805
  processor_instance = Processor(
487
806
  name=processor_name,
488
- stream=processor_name, # Default stream name to processor name
807
+ stream=processor_name,
489
808
  namespace=namespace,
490
809
  labels=labels,
491
810
  container=container_request,
@@ -496,7 +815,9 @@ def processor(
496
815
  scale_config=scale,
497
816
  no_delete=no_delete,
498
817
  )
499
-
818
+ print(
819
+ f"[DEBUG Decorator] Processor instance '{processor_name}' created successfully."
820
+ )
500
821
  return processor_instance
501
822
 
502
823
  return decorator
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.16
3
+ Version: 0.1.17
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
+ Requires-Dist: dill>=0.3.8
8
9
  Requires-Dist: openai>=1.68.2
9
10
  Requires-Dist: pydantic>=2.10.6
11
+ Requires-Dist: pysocks>=1.7.1
10
12
  Requires-Dist: pyyaml>=6.0.2
13
+ Requires-Dist: redis[socks]>=5.0
11
14
  Requires-Dist: requests>=2.32.3
12
15
  Dynamic: license-file
13
16
 
@@ -3,18 +3,18 @@ nebu/auth.py,sha256=rApCd-7_c3GpIb7gjCB79rR7SOcmkG7MmaTE6zMbvr0,1125
3
3
  nebu/config.py,sha256=XBY7uKgcJX9d1HGxqqpx87o_9DuF3maUlUnKkcpUrKU,4565
4
4
  nebu/meta.py,sha256=CzFHMND9seuewzq9zNNx9WTr6JvrCBExe7BLqDSr7lM,745
5
5
  nebu/containers/container.py,sha256=yb7KaPTVXnEEAlrpdlUi4HNqF6P7z9bmwAILGlq6iqU,13502
6
- nebu/containers/decorator.py,sha256=qiM7hbHne9MhSp1gDgX5z5bimsXr_YPjTIZoe09dwr4,2741
6
+ nebu/containers/decorator.py,sha256=uFtzlAXRHYZECJ-NPusY7oN9GXvdHrHDd_JNrIGr8aQ,3244
7
7
  nebu/containers/models.py,sha256=0j6NGy4yto-enRDh_4JH_ZTbHrLdSpuMOqNQPnIrwC4,6815
8
8
  nebu/containers/server.py,sha256=yFa2Y9PzBn59E1HftKiv0iapPonli2rbGAiU6r-wwe0,2513
9
- nebu/processors/consumer.py,sha256=LsMfh-Ai1goxolQiw913Tj9bYLR0Ji3OXp3LGFFoiQ8,15480
10
- nebu/processors/decorate.py,sha256=NwuV0uY1weAM472VntfyYKd0tTsqtFCPrNGhF4vJP6I,20492
9
+ nebu/processors/consumer.py,sha256=0HJxRLoeRdN4xY6bjIxqr5bD5JpFSyKb5s-eS5oTy9s,16063
10
+ nebu/processors/decorate.py,sha256=BlpRF9u-1q9IS-UDn0XFzoYIBqEU4Xkjkoge394exJs,34786
11
11
  nebu/processors/default.py,sha256=W4slJenG59rvyTlJ7gRp58eFfXcNOTT2Hfi6zzJAobI,365
12
12
  nebu/processors/models.py,sha256=GvnI8UJrQSjHo2snP07cPfisCH90cEGTY-PZV5_AtXI,3654
13
13
  nebu/processors/processor.py,sha256=oy2YdI-cy6qQWxrZhpZahJV46oWZlu_Im-jm811R_oo,9667
14
14
  nebu/redis/models.py,sha256=coPovAcVXnOU1Xh_fpJL4PO3QctgK9nBe5QYoqEcnxg,1230
15
15
  nebu/services/service.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- nebu-0.1.16.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- nebu-0.1.16.dist-info/METADATA,sha256=TN23ux4MnSCtVx4I23hiai9S-ROhFmza8-qCAw0f9Pw,1588
18
- nebu-0.1.16.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
19
- nebu-0.1.16.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
20
- nebu-0.1.16.dist-info/RECORD,,
16
+ nebu-0.1.17.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ nebu-0.1.17.dist-info/METADATA,sha256=m9oiMDrMt5x1x7wa51dsxwgzj-tUcX_hZrQrGN8NtyE,1678
18
+ nebu-0.1.17.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
19
+ nebu-0.1.17.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
20
+ nebu-0.1.17.dist-info/RECORD,,
File without changes