nebu 0.1.105__py3-none-any.whl → 0.1.108__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.
nebu/data.py CHANGED
@@ -59,7 +59,7 @@ def rclone_copy(
59
59
  source_dir,
60
60
  destination,
61
61
  f"--transfers={transfers}",
62
- "--progress",
62
+ # "--progress",
63
63
  ]
64
64
 
65
65
  if dry_run:
@@ -1171,7 +1171,7 @@ class RcloneBucket(StorageBucket):
1171
1171
  "--modify-window=2s",
1172
1172
  "--log-level=DEBUG" if self.verbose else "--log-level=INFO",
1173
1173
  "--log-format=date,time,level,message",
1174
- "--progress", # Add progress display
1174
+ # "--progress", # Add progress display
1175
1175
  ]
1176
1176
  if dry_run:
1177
1177
  rc_args.append("--dry-run")
@@ -1286,7 +1286,7 @@ class RcloneBucket(StorageBucket):
1286
1286
  rc_args = [
1287
1287
  "--log-level=DEBUG" if self.verbose else "--log-level=INFO",
1288
1288
  "--log-format=date,time,level,message",
1289
- "--progress", # Add progress display
1289
+ # "--progress", # Add progress display
1290
1290
  ]
1291
1291
 
1292
1292
  # Set environment variables for AWS credentials if they exist
@@ -366,6 +366,8 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
366
366
  global target_function, imported_module, local_namespace
367
367
  global execution_mode, r, REDIS_STREAM, REDIS_CONSUMER_GROUP
368
368
 
369
+ print(f">>> Processing message {message_id}")
370
+
369
371
  # --- Subprocess Execution Path ---
370
372
  if execution_mode == "subprocess":
371
373
  logger.info(f"Processing message {message_id} in subprocess...")
@@ -610,8 +612,15 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
610
612
  logger.debug(f">> Raw payload: {raw_payload}")
611
613
 
612
614
  # --- Extract fields from the *inner* message content for HealthCheck and regular processing ---
613
- # The actual message content is inside raw_payload[\"content\"]
615
+ # The actual message content is inside raw_payload["content"]
614
616
  inner_content_data = raw_payload.get("content", {})
617
+
618
+ # Add debug logging for content structure analysis
619
+ logger.debug(f">> inner_content_data type: {type(inner_content_data)}")
620
+ logger.debug(
621
+ f">> inner_content_data keys (if dict): {list(inner_content_data.keys()) if isinstance(inner_content_data, dict) else 'N/A'}"
622
+ )
623
+
615
624
  if not isinstance(inner_content_data, dict):
616
625
  # If content is not a dict (e.g. already a primitive from a non-Message processor)
617
626
  # we can't reliably get 'kind' or other fields from it.
@@ -625,11 +634,44 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
625
634
  actual_content_to_process = inner_content_data # Use it as is
626
635
  inner_created_at_str = None
627
636
  else:
628
- inner_kind = inner_content_data.get("kind", "")
629
- inner_msg_id = inner_content_data.get("id", "")
630
- # The 'content' field of the inner_content_data is what the user function expects
631
- actual_content_to_process = inner_content_data.get("content", {})
632
- inner_created_at_str = inner_content_data.get("created_at")
637
+ # Check if this looks like a nested message structure (has kind/id/content fields)
638
+ # vs direct content data
639
+ has_message_structure = (
640
+ "kind" in inner_content_data
641
+ and "id" in inner_content_data
642
+ and "content" in inner_content_data
643
+ )
644
+
645
+ logger.debug(f">> has_message_structure: {has_message_structure}")
646
+
647
+ if has_message_structure:
648
+ # Nested message structure: extract from inner message
649
+ inner_kind = inner_content_data.get("kind", "")
650
+ inner_msg_id = inner_content_data.get("id", "")
651
+ actual_content_to_process = inner_content_data.get("content", {})
652
+ inner_created_at_str = inner_content_data.get("created_at")
653
+ logger.debug(
654
+ f">> Using nested structure - inner_kind: {inner_kind}, inner_msg_id: {inner_msg_id}"
655
+ )
656
+ logger.debug(
657
+ f">> actual_content_to_process keys: {list(actual_content_to_process.keys())}"
658
+ )
659
+ else:
660
+ # Direct content structure: the content data is directly in inner_content_data
661
+ inner_kind = raw_payload.get("kind", "") # Get kind from outer payload
662
+ inner_msg_id = raw_payload.get("id", "") # Get id from outer payload
663
+ actual_content_to_process = (
664
+ inner_content_data # Use inner_content_data directly
665
+ )
666
+ inner_created_at_str = raw_payload.get(
667
+ "created_at"
668
+ ) # Get created_at from outer payload
669
+ logger.debug(
670
+ f">> Using direct structure - inner_kind: {inner_kind}, inner_msg_id: {inner_msg_id}"
671
+ )
672
+ logger.debug(
673
+ f">> actual_content_to_process keys: {list(actual_content_to_process.keys())}"
674
+ )
633
675
 
634
676
  # Attempt to parse inner_created_at, fallback to now()
635
677
  try:
@@ -685,12 +727,24 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
685
727
  if isinstance(actual_content_to_process, str):
686
728
  try:
687
729
  content_for_validation = json.loads(actual_content_to_process)
730
+ logger.debug(
731
+ f">> Parsed JSON string content_for_validation keys: {list(content_for_validation.keys()) if isinstance(content_for_validation, dict) else 'N/A'}"
732
+ )
688
733
  except json.JSONDecodeError:
689
734
  content_for_validation = (
690
735
  actual_content_to_process # Keep as string if not valid JSON
691
736
  )
737
+ logger.debug(
738
+ f">> Failed to parse JSON, keeping as string: {type(actual_content_to_process)}"
739
+ )
692
740
  else:
693
741
  content_for_validation = actual_content_to_process
742
+ logger.debug(
743
+ f">> Using actual_content_to_process directly: {type(content_for_validation)}"
744
+ )
745
+ logger.debug(
746
+ f">> content_for_validation keys: {list(content_for_validation.keys())}"
747
+ )
694
748
 
695
749
  # --- Construct Input Object using Imported Types ---
696
750
  input_obj: Any = None
@@ -736,9 +790,16 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
736
790
 
737
791
  if content_model_class:
738
792
  try:
793
+ logger.debug(
794
+ f">> Attempting to validate content with {content_model_class.__name__}"
795
+ )
796
+ logger.debug(
797
+ f">> Content being validated: {json.dumps(content_for_validation, indent=2) if isinstance(content_for_validation, dict) else str(content_for_validation)}"
798
+ )
739
799
  content_model = content_model_class.model_validate(
740
800
  content_for_validation
741
801
  )
802
+ logger.debug(f">> Successfully validated content model")
742
803
  # print(f"Validated content model: {content_model}")
743
804
  input_obj = message_class(
744
805
  kind=inner_kind,
@@ -752,10 +813,16 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
752
813
  adapter=adapter,
753
814
  api_key=api_key,
754
815
  )
816
+ logger.debug(
817
+ f">> Successfully created Message object with validated content"
818
+ )
755
819
  except Exception as e:
756
820
  logger.error(
757
821
  f"Error validating/creating content model '{content_type_name}': {e}. Falling back."
758
822
  )
823
+ logger.debug(
824
+ f">> Content validation failed for: {json.dumps(content_for_validation, indent=2) if isinstance(content_for_validation, dict) else str(content_for_validation)}"
825
+ )
759
826
  # Fallback to raw content in Message
760
827
  input_obj = message_class(
761
828
  kind=inner_kind,
@@ -769,8 +836,12 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
769
836
  adapter=adapter,
770
837
  api_key=api_key,
771
838
  )
839
+ logger.debug(
840
+ f">> Created Message object with raw content fallback"
841
+ )
772
842
  else:
773
843
  # No content type name or class found, use raw content
844
+ logger.debug(f">> No content model class found, using raw content")
774
845
  input_obj = message_class(
775
846
  kind=inner_kind,
776
847
  id=inner_msg_id,
@@ -783,6 +854,9 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
783
854
  adapter=adapter,
784
855
  api_key=api_key,
785
856
  )
857
+ logger.debug(
858
+ f">> Created Message object with raw content (no content model class)"
859
+ )
786
860
  else: # Not a stream message, use the function's parameter type
787
861
  param_type_name = (
788
862
  param_type_str # Assume param_type_str holds the class name
@@ -844,7 +918,15 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
844
918
  # Safe logging that avoids __repr__ issues with BaseModel objects
845
919
  try:
846
920
  if hasattr(input_obj, "model_dump"):
847
- logger.debug(f"Input object (BaseModel): {input_obj.model_dump()}")
921
+ try:
922
+ # Use model_dump_json for safer serialization that handles nested objects
923
+ logger.debug(
924
+ f"Input object (BaseModel): {input_obj.model_dump_json()}"
925
+ )
926
+ except Exception as dump_e:
927
+ logger.debug(
928
+ f"Input object: <BaseModel object of type {type(input_obj).__name__}> (model_dump failed: {dump_e})"
929
+ )
848
930
  else:
849
931
  logger.debug(f"Input object: {input_obj}")
850
932
  except Exception as log_e:
@@ -854,15 +936,13 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
854
936
 
855
937
  # Execute the function
856
938
  logger.info("Executing function...")
939
+
940
+ # Add warning about potential print statement issues in user code
941
+ logger.debug(
942
+ ">> About to execute user function - note: print statements in user code may fail if the Message object has validation issues"
943
+ )
944
+
857
945
  result = target_function(input_obj)
858
- # Safe logging that avoids __repr__ issues with BaseModel objects if this debug line is uncommented
859
- # try:
860
- # if hasattr(result, "model_dump"):
861
- # logger.debug(f"Raw Result (BaseModel): {result.model_dump()}")
862
- # else:
863
- # logger.debug(f"Raw Result: {result}")
864
- # except Exception as log_e:
865
- # logger.debug(f"Raw Result: <object of type {type(result).__name__}> (repr failed: {log_e})")
866
946
 
867
947
  result_content = None # Default to None
868
948
  if result is not None: # Only process if there's a result
@@ -2,7 +2,7 @@ import json
2
2
  import threading
3
3
  import time
4
4
  import uuid
5
- from typing import Any, Dict, Generic, List, Optional, TypeVar
5
+ from typing import Any, Dict, Generic, List, Optional, TypeVar, cast, get_args
6
6
 
7
7
  import requests
8
8
  from pydantic import BaseModel
@@ -132,6 +132,30 @@ class Processor(Generic[InputType, OutputType]):
132
132
  self.processors_url = f"{self.orign_host}/v1/processors"
133
133
  self._log_thread: Optional[threading.Thread] = None
134
134
 
135
+ # Attempt to infer OutputType if schema_ is not provided
136
+ if self.schema_ is None and hasattr(self, "__orig_class__"):
137
+ type_args = get_args(self.__orig_class__) # type: ignore
138
+ if len(type_args) == 2:
139
+ output_type_candidate = type_args[1]
140
+ # Check if it looks like a Pydantic model class
141
+ if isinstance(output_type_candidate, type) and issubclass(
142
+ output_type_candidate, BaseModel
143
+ ):
144
+ logger.debug(
145
+ f"Inferred OutputType {output_type_candidate.__name__} from generic arguments."
146
+ )
147
+ self.schema_ = output_type_candidate
148
+ else:
149
+ logger.debug(
150
+ f"Second generic argument {output_type_candidate} is not a Pydantic BaseModel. "
151
+ "Cannot infer OutputType."
152
+ )
153
+ else:
154
+ logger.debug(
155
+ "Could not infer OutputType from generic arguments: wrong number of type args found "
156
+ f"(expected 2, got {len(type_args) if type_args else 0})."
157
+ )
158
+
135
159
  # Fetch existing Processors
136
160
  response = requests.get(
137
161
  self.processors_url, headers={"Authorization": f"Bearer {self.api_key}"}
@@ -222,12 +246,18 @@ class Processor(Generic[InputType, OutputType]):
222
246
  logs: bool = False,
223
247
  api_key: Optional[str] = None,
224
248
  user_key: Optional[str] = None,
249
+ timeout: Optional[float] = 600.0,
225
250
  ) -> OutputType | Dict[str, Any] | None:
226
251
  """
227
252
  Allows the Processor instance to be called like a function, sending data.
228
253
  """
229
254
  return self.send(
230
- data=data, wait=wait, logs=logs, api_key=api_key, user_key=user_key
255
+ data=data,
256
+ wait=wait,
257
+ logs=logs,
258
+ api_key=api_key,
259
+ user_key=user_key,
260
+ timeout=timeout,
231
261
  )
232
262
 
233
263
  def send(
@@ -271,7 +301,9 @@ class Processor(Generic[InputType, OutputType]):
271
301
  timeout=timeout,
272
302
  )
273
303
  response.raise_for_status()
274
- send_response_json = response.json()
304
+ raw_response_json = response.json()
305
+ raw_content = raw_response_json.get("content")
306
+ logger.debug(f">>> Raw content: {raw_content}")
275
307
 
276
308
  # --- Fetch Logs (if requested and not already running) ---
277
309
  if logs:
@@ -297,7 +329,33 @@ class Processor(Generic[InputType, OutputType]):
297
329
  else:
298
330
  logger.info(f"Log fetching is already running for {processor_name}.")
299
331
 
300
- return send_response_json
332
+ # Attempt to parse into OutputType if conditions are met
333
+ if (
334
+ wait
335
+ and self.schema_
336
+ and isinstance(self.schema_, type)
337
+ and issubclass(self.schema_, BaseModel) # type: ignore
338
+ and isinstance(raw_content, dict)
339
+ ): # Check if raw_content is a dict
340
+ try:
341
+ # self.schema_ is assumed to be the Pydantic model class for OutputType
342
+ # Parse raw_content instead of the full response
343
+ parsed_model = self.schema_.model_validate(raw_content)
344
+ # Cast to OutputType to satisfy the linter with generics
345
+ parsed_output: OutputType = cast(OutputType, parsed_model)
346
+ return parsed_output
347
+ except (
348
+ Exception
349
+ ) as e: # Consider pydantic.ValidationError for more specific handling
350
+ schema_name = getattr(self.schema_, "__name__", str(self.schema_))
351
+ logger.error(
352
+ f"Processor {processor_name}: Failed to parse 'content' field into output type {schema_name}. "
353
+ f"Error: {e}. Returning raw JSON response."
354
+ )
355
+ # Fallback to returning the raw JSON response
356
+ return raw_content
357
+
358
+ return raw_content
301
359
 
302
360
  def scale(self, replicas: int) -> Dict[str, Any]:
303
361
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.105
3
+ Version: 0.1.108
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -2,7 +2,7 @@ nebu/__init__.py,sha256=xNtWiN29MJZK_WBEUP-9hDmlkfLxoASVI-f4tNTXO58,454
2
2
  nebu/auth.py,sha256=N_v6SPFD9HU_UoRDTaouH03g2Hmo9C-xxqInE1FweXE,1471
3
3
  nebu/cache.py,sha256=JqRb4FdZrRrO4ePlwvsKdxRC8dNEFMxfTWag0aJz8Gw,4893
4
4
  nebu/config.py,sha256=C5Jt9Bd0i0HrgzBSVNJ-Ml3KwX_gaYbYYZEtNL2gvJg,7031
5
- nebu/data.py,sha256=4fEvO_xNCeTNW4RFr1u6iZ7j92S-5zWwxmFdXopxae8,63502
5
+ nebu/data.py,sha256=v29F1Dwfa3cw6HzG2C6bxAfAJIu16k5PwZCC_niLwCU,63508
6
6
  nebu/errors.py,sha256=bBnK5YQ6qZg4OMY81AN2k03ppefg89FUwF_SHEMlqCA,170
7
7
  nebu/logging.py,sha256=VzpjCEoXm3c4i0sKJL5GTsPIhTQ6Y4BPUTzPmwhve7o,950
8
8
  nebu/meta.py,sha256=CzFHMND9seuewzq9zNNx9WTr6JvrCBExe7BLqDSr7lM,745
@@ -13,16 +13,16 @@ nebu/containers/container.py,sha256=Mrh_gvMsTvDkj3CwpqIPzJ72IMw0gQIg64y548vq0yg,
13
13
  nebu/containers/models.py,sha256=0j6NGy4yto-enRDh_4JH_ZTbHrLdSpuMOqNQPnIrwC4,6815
14
14
  nebu/namespaces/models.py,sha256=EqUOpzhVBhvJw2P92ONDUbIgC31M9jMmcaG5vyOrsWg,497
15
15
  nebu/namespaces/namespace.py,sha256=oeZyGqsIGIrppyjif1ZONsdTmqRgd9oSLFE1BChXTTE,5247
16
- nebu/processors/consumer.py,sha256=v5JsuvEEUUBbe8sKGB2QjHca8FPHMIjq9Ntk32Uf3G4,58674
16
+ nebu/processors/consumer.py,sha256=9WapzBTPuXRunH-vjPerTlGZy__hn_d4m13l1ajebY8,62732
17
17
  nebu/processors/consumer_process_worker.py,sha256=h--eNFKaLbUayxn88mB8oGGdrU2liE1dnwm_TPlewX8,36960
18
18
  nebu/processors/decorate.py,sha256=AfHVCoNbW7RymccF5ewleEL-GlMiqVH1-t9bCmD60rk,58654
19
19
  nebu/processors/default.py,sha256=cy4ETMdbdRGkrvbYec1o60h7mGDlGN5JsuUph0ENtDU,364
20
20
  nebu/processors/models.py,sha256=g4B1t6Rgoy-NUEHBLeQc0EENzHXLDlWSio8Muv7cTDU,4093
21
- nebu/processors/processor.py,sha256=vbafqawdZhh5VRoBE5C5wRR_32iSyzoDTqK7qsNQvaM,19242
21
+ nebu/processors/processor.py,sha256=PeJvxBPfjQ8uFqLcrGNk28yqBmqZC2VW4ssCsanZxYY,21985
22
22
  nebu/redis/models.py,sha256=coPovAcVXnOU1Xh_fpJL4PO3QctgK9nBe5QYoqEcnxg,1230
23
23
  nebu/services/service.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- nebu-0.1.105.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
- nebu-0.1.105.dist-info/METADATA,sha256=B9wVIWPowGZHwobFzrRBymh14aUiwL_GAFTNRR80roo,1798
26
- nebu-0.1.105.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
27
- nebu-0.1.105.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
28
- nebu-0.1.105.dist-info/RECORD,,
24
+ nebu-0.1.108.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
+ nebu-0.1.108.dist-info/METADATA,sha256=drUdR1bXazQ1Yqlns8I1KPOt1qnoO6OhNQMAlDwaK9o,1798
26
+ nebu-0.1.108.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
27
+ nebu-0.1.108.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
28
+ nebu-0.1.108.dist-info/RECORD,,
File without changes