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 +3 -3
- nebu/processors/consumer.py +95 -15
- nebu/processors/processor.py +62 -4
- {nebu-0.1.105.dist-info → nebu-0.1.108.dist-info}/METADATA +1 -1
- {nebu-0.1.105.dist-info → nebu-0.1.108.dist-info}/RECORD +8 -8
- {nebu-0.1.105.dist-info → nebu-0.1.108.dist-info}/WHEEL +0 -0
- {nebu-0.1.105.dist-info → nebu-0.1.108.dist-info}/licenses/LICENSE +0 -0
- {nebu-0.1.105.dist-info → nebu-0.1.108.dist-info}/top_level.txt +0 -0
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
|
nebu/processors/consumer.py
CHANGED
@@ -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[
|
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
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
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
|
-
|
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
|
nebu/processors/processor.py
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
"""
|
@@ -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=
|
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=
|
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=
|
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.
|
25
|
-
nebu-0.1.
|
26
|
-
nebu-0.1.
|
27
|
-
nebu-0.1.
|
28
|
-
nebu-0.1.
|
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
|
File without changes
|
File without changes
|