nebu 0.1.49__tar.gz → 0.1.52__tar.gz
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-0.1.49/src/nebu.egg-info → nebu-0.1.52}/PKG-INFO +1 -1
- {nebu-0.1.49 → nebu-0.1.52}/pyproject.toml +1 -1
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/consumer.py +9 -5
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/decorate.py +118 -51
- {nebu-0.1.49 → nebu-0.1.52/src/nebu.egg-info}/PKG-INFO +1 -1
- {nebu-0.1.49 → nebu-0.1.52}/LICENSE +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/README.md +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/setup.cfg +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/__init__.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/auth.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/builders/builder.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/builders/models.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/cache.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/config.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/container.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/decorator.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/models.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/server.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/data.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/meta.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/default.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/models.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/processor.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/remote.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/redis/models.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu/services/service.py +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/SOURCES.txt +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/dependency_links.txt +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/requires.txt +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/top_level.txt +0 -0
- {nebu-0.1.49 → nebu-0.1.52}/tests/test_containers.py +0 -0
@@ -6,7 +6,7 @@ import socket
|
|
6
6
|
import sys
|
7
7
|
import time
|
8
8
|
import traceback
|
9
|
-
import types
|
9
|
+
import types
|
10
10
|
from datetime import datetime, timezone
|
11
11
|
from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, cast
|
12
12
|
|
@@ -350,7 +350,9 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
|
|
350
350
|
user_id = None
|
351
351
|
try:
|
352
352
|
payload_str = message_data.get("data")
|
353
|
-
if
|
353
|
+
if (
|
354
|
+
not payload_str
|
355
|
+
): # Covers None and empty string, isinstance check is redundant
|
354
356
|
raise ValueError(
|
355
357
|
f"Missing or invalid 'data' field (expected non-empty string): {message_data}"
|
356
358
|
)
|
@@ -374,6 +376,7 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
|
|
374
376
|
created_at = (
|
375
377
|
datetime.fromisoformat(created_at_str)
|
376
378
|
if created_at_str
|
379
|
+
and isinstance(created_at_str, str) # Check type explicitly
|
377
380
|
else datetime.now(timezone.utc)
|
378
381
|
)
|
379
382
|
except ValueError:
|
@@ -651,6 +654,7 @@ consumer_name = f"consumer-{os.getpid()}-{socket.gethostname()}" # More unique
|
|
651
654
|
|
652
655
|
try:
|
653
656
|
while True:
|
657
|
+
print("reading from stream...")
|
654
658
|
try:
|
655
659
|
# --- Check for Code Updates ---
|
656
660
|
if entrypoint_abs_path: # Should always be set after init
|
@@ -722,13 +726,13 @@ try:
|
|
722
726
|
assert isinstance(REDIS_STREAM, str)
|
723
727
|
assert isinstance(REDIS_CONSUMER_GROUP, str)
|
724
728
|
|
725
|
-
|
729
|
+
streams_arg: Dict[str, str] = {REDIS_STREAM: ">"}
|
730
|
+
|
726
731
|
# With decode_responses=True, redis-py expects str types here
|
727
|
-
streams_arg = {REDIS_STREAM: ">"}
|
728
732
|
messages = r.xreadgroup(
|
729
733
|
REDIS_CONSUMER_GROUP,
|
730
734
|
consumer_name,
|
731
|
-
streams_arg,
|
735
|
+
streams_arg, # type: ignore[arg-type]
|
732
736
|
count=1,
|
733
737
|
block=5000, # Use milliseconds for block
|
734
738
|
)
|
@@ -1,11 +1,7 @@
|
|
1
1
|
import ast # For parsing notebook code
|
2
2
|
import inspect
|
3
|
-
import
|
4
|
-
import os # Add os import
|
5
|
-
import re # Import re for fallback check
|
6
|
-
import tempfile # Add tempfile import
|
3
|
+
import os
|
7
4
|
import textwrap
|
8
|
-
import uuid # Add uuid import
|
9
5
|
from typing import (
|
10
6
|
Any,
|
11
7
|
Callable,
|
@@ -17,15 +13,15 @@ from typing import (
|
|
17
13
|
get_origin,
|
18
14
|
get_type_hints,
|
19
15
|
)
|
20
|
-
from urllib.parse import urlparse
|
16
|
+
from urllib.parse import urlparse
|
21
17
|
|
22
|
-
import dill
|
23
|
-
import requests
|
24
|
-
from botocore.exceptions import ClientError
|
18
|
+
import dill
|
19
|
+
import requests
|
20
|
+
from botocore.exceptions import ClientError
|
25
21
|
from pydantic import BaseModel
|
26
22
|
|
27
|
-
from nebu.auth import get_user_profile
|
28
|
-
from nebu.config import GlobalConfig
|
23
|
+
from nebu.auth import get_user_profile
|
24
|
+
from nebu.config import GlobalConfig
|
29
25
|
from nebu.containers.models import (
|
30
26
|
V1AuthzConfig,
|
31
27
|
V1ContainerHealthCheck,
|
@@ -38,7 +34,7 @@ from nebu.containers.models import (
|
|
38
34
|
V1VolumeDriver,
|
39
35
|
V1VolumePath,
|
40
36
|
)
|
41
|
-
from nebu.data import Bucket
|
37
|
+
from nebu.data import Bucket
|
42
38
|
from nebu.meta import V1ResourceMetaRequest
|
43
39
|
from nebu.processors.models import (
|
44
40
|
Message,
|
@@ -82,7 +78,7 @@ def is_jupyter_notebook():
|
|
82
78
|
import IPython # Now safe to import
|
83
79
|
|
84
80
|
ip = IPython.get_ipython()
|
85
|
-
if ip is None:
|
81
|
+
if ip is None: # type: ignore
|
86
82
|
# print("[DEBUG Helper] is_jupyter_notebook: No IPython instance found.")
|
87
83
|
return False
|
88
84
|
class_name = str(ip.__class__)
|
@@ -93,7 +89,9 @@ def is_jupyter_notebook():
|
|
93
89
|
# print("[DEBUG Helper] is_jupyter_notebook: Not Jupyter (IPython instance found, but not ZMQInteractiveShell).")
|
94
90
|
return False
|
95
91
|
except Exception as e:
|
96
|
-
|
92
|
+
print(
|
93
|
+
f"[DEBUG Helper] is_jupyter_notebook: Exception occurred: {e}"
|
94
|
+
) # Reduce verbosity
|
97
95
|
return False
|
98
96
|
|
99
97
|
|
@@ -164,9 +162,10 @@ def extract_definition_source_from_string(
|
|
164
162
|
found_in_cell = False
|
165
163
|
for node in ast.walk(tree):
|
166
164
|
if (
|
167
|
-
isinstance(
|
168
|
-
|
169
|
-
|
165
|
+
isinstance(
|
166
|
+
node, def_type
|
167
|
+
) # Check if it's the right type (FuncDef or ClassDef)
|
168
|
+
and getattr(node, "name", None) == def_name # Safely check name
|
170
169
|
):
|
171
170
|
print(
|
172
171
|
f"[DEBUG Helper] extract: Found node for '{def_name}' in cell #{cell_num}."
|
@@ -189,6 +188,8 @@ def extract_definition_source_from_string(
|
|
189
188
|
end_lineno = getattr(node, "end_lineno", start_lineno + 1)
|
190
189
|
|
191
190
|
if hasattr(node, "decorator_list") and node.decorator_list:
|
191
|
+
# Ensure it's a node type that *can* have decorators
|
192
|
+
# FunctionDef and ClassDef have decorator_list
|
192
193
|
first_decorator_start_line = (
|
193
194
|
getattr(
|
194
195
|
node.decorator_list[0], "lineno", start_lineno + 1
|
@@ -501,8 +502,8 @@ def processor(
|
|
501
502
|
"Missing required fields (access_key_id, secret_access_key, s3_base_uri) in S3 token response."
|
502
503
|
)
|
503
504
|
|
504
|
-
# Construct unique S3 path: s3://<base_bucket>/<base_prefix>/<code_prefix>/<processor_name
|
505
|
-
unique_suffix = f"{
|
505
|
+
# Construct unique S3 path: s3://<base_bucket>/<base_prefix>/<code_prefix>/<namespace>/<processor_name>/
|
506
|
+
unique_suffix = f"{effective_namespace}/{processor_name}"
|
506
507
|
parsed_base = urlparse(s3_base_uri)
|
507
508
|
if not parsed_base.scheme == "s3" or not parsed_base.netloc:
|
508
509
|
raise ValueError(f"Invalid s3_base_uri received: {s3_base_uri}")
|
@@ -545,7 +546,7 @@ def processor(
|
|
545
546
|
s3_bucket.sync(
|
546
547
|
source=func_dir,
|
547
548
|
destination=s3_destination_uri,
|
548
|
-
delete=
|
549
|
+
delete=True,
|
549
550
|
dry_run=False,
|
550
551
|
)
|
551
552
|
print("[DEBUG Decorator] S3 code upload completed.")
|
@@ -605,21 +606,38 @@ def processor(
|
|
605
606
|
# print(f"[DEBUG Decorator] Added string source to env for included obj: {obj_name_str}")
|
606
607
|
elif isinstance(obj_source, tuple):
|
607
608
|
# Handle tuple source (origin, args) - assumes get_model_source/get_type_source logic
|
608
|
-
|
609
|
-
if
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
if isinstance(arg_src, str):
|
609
|
+
# Ensure obj_source is indeed a tuple before unpacking
|
610
|
+
if len(obj_source) == 2:
|
611
|
+
# Now safe to unpack
|
612
|
+
origin_src, arg_srcs = obj_source
|
613
|
+
# type: ignore[misc] # Suppress persistent tuple unpacking error
|
614
|
+
if origin_src and isinstance(origin_src, str):
|
615
615
|
all_env.append(
|
616
616
|
V1EnvVar(
|
617
|
-
key=f"{env_key_base}
|
618
|
-
value=arg_src,
|
617
|
+
key=f"{env_key_base}_SOURCE", value=origin_src
|
619
618
|
)
|
620
619
|
)
|
621
|
-
# Handle
|
622
|
-
|
620
|
+
# Handle arg_srcs (this part seems okay)
|
621
|
+
if isinstance(arg_srcs, list):
|
622
|
+
for j, arg_src in enumerate(arg_srcs):
|
623
|
+
if isinstance(arg_src, str):
|
624
|
+
all_env.append(
|
625
|
+
V1EnvVar(
|
626
|
+
key=f"{env_key_base}_ARG_{j}_SOURCE",
|
627
|
+
value=arg_src,
|
628
|
+
)
|
629
|
+
)
|
630
|
+
else:
|
631
|
+
print(
|
632
|
+
f"[DEBUG Decorator] Warning: Expected arg_srcs to be a list, got {type(arg_srcs)}"
|
633
|
+
)
|
634
|
+
else:
|
635
|
+
# Handle unexpected type or structure for obj_source if necessary
|
636
|
+
# For now, assume it fits the expected tuple structure if isinstance passes
|
637
|
+
# origin_src, arg_srcs = None, [] # Default/error state (already covered by outer check)
|
638
|
+
print(
|
639
|
+
f"[DEBUG Decorator] Warning: Unexpected obj_source structure: {obj_source}"
|
640
|
+
)
|
623
641
|
else:
|
624
642
|
print(
|
625
643
|
f"Warning: Unknown source type for included object {obj_name_str}: {type(obj_source)}"
|
@@ -826,19 +844,44 @@ def processor(
|
|
826
844
|
# Add: Included object sources (if any)
|
827
845
|
# Add: INIT_FUNC_NAME (if provided)
|
828
846
|
|
829
|
-
#
|
830
|
-
|
847
|
+
# Calculate module_path based on relative file path
|
848
|
+
calculated_module_path = None
|
831
849
|
if rel_func_path:
|
832
850
|
# Convert OS-specific path to module path (e.g., subdir/file.py -> subdir.file)
|
833
|
-
|
834
|
-
if
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
851
|
+
base, ext = os.path.splitext(rel_func_path)
|
852
|
+
if ext == ".py":
|
853
|
+
module_path_parts = base.split(os.sep)
|
854
|
+
if module_path_parts[-1] == "__init__":
|
855
|
+
module_path_parts.pop() # Remove __init__
|
856
|
+
# Filter out potential empty strings if path started with / or had //
|
857
|
+
module_path_parts = [part for part in module_path_parts if part]
|
858
|
+
calculated_module_path = ".".join(module_path_parts)
|
859
|
+
else:
|
860
|
+
# Not a python file? Should not happen based on inspect.getfile
|
861
|
+
print(
|
862
|
+
f"[DEBUG Decorator] Warning: Function source file is not a .py file: {rel_func_path}"
|
863
|
+
)
|
864
|
+
# Set calculated_module_path to None explicitly to trigger fallback later
|
865
|
+
calculated_module_path = None
|
866
|
+
else:
|
867
|
+
# Should have errored earlier if rel_func_path is None
|
868
|
+
print(
|
869
|
+
"[DEBUG Decorator] Warning: Could not determine relative function path. Falling back to func.__module__."
|
870
|
+
)
|
871
|
+
# Set calculated_module_path to None explicitly to trigger fallback later
|
872
|
+
calculated_module_path = None
|
873
|
+
|
874
|
+
# Assign final module_path using fallback if calculation failed or wasn't applicable
|
875
|
+
if calculated_module_path is not None:
|
876
|
+
module_path = calculated_module_path
|
877
|
+
print(f"[DEBUG Decorator] Using calculated module path: {module_path}")
|
878
|
+
else:
|
879
|
+
module_path = func.__module__ # Fallback
|
880
|
+
print(f"[DEBUG Decorator] Falling back to func.__module__: {module_path}")
|
841
881
|
|
882
|
+
# Basic info needed by consumer to find and run the function
|
883
|
+
all_env.append(V1EnvVar(key="FUNCTION_NAME", value=processor_name))
|
884
|
+
if rel_func_path:
|
842
885
|
# For now, just pass the relative file path, consumer will handle conversion
|
843
886
|
all_env.append(
|
844
887
|
V1EnvVar(key="NEBU_ENTRYPOINT_MODULE_PATH", value=rel_func_path)
|
@@ -846,9 +889,7 @@ def processor(
|
|
846
889
|
print(
|
847
890
|
f"[DEBUG Decorator] Set NEBU_ENTRYPOINT_MODULE_PATH to: {rel_func_path}"
|
848
891
|
)
|
849
|
-
else
|
850
|
-
# Should have errored earlier if rel_func_path is None
|
851
|
-
raise RuntimeError("Internal error: Relative function path not determined.")
|
892
|
+
# No else needed, handled by fallback calculation above
|
852
893
|
|
853
894
|
if init_func:
|
854
895
|
init_func_name = init_func.__name__ # Get name here
|
@@ -862,26 +903,52 @@ def processor(
|
|
862
903
|
print(f"[DEBUG Decorator] Set INIT_FUNC_NAME to: {init_func_name}")
|
863
904
|
|
864
905
|
# Type info (still useful for deserialization/validation in consumer)
|
906
|
+
# Adjust type strings to replace '__main__' with the calculated module path
|
907
|
+
param_type_str_repr = str(param_type)
|
908
|
+
if module_path != "__main__" and "__main__." in param_type_str_repr:
|
909
|
+
# Be careful with replacement - replace only module prefix
|
910
|
+
# Example: "<class '__main__.MyModel'>" -> "<class 'mymodule.MyModel'>"
|
911
|
+
# Example: "typing.Optional[__main__.MyModel]" -> "typing.Optional[mymodule.MyModel]"
|
912
|
+
param_type_str_repr = param_type_str_repr.replace(
|
913
|
+
"__main__.", f"{module_path}."
|
914
|
+
)
|
915
|
+
print(
|
916
|
+
f"[DEBUG Decorator] Adjusted param type string: {param_type_str_repr}"
|
917
|
+
)
|
918
|
+
|
865
919
|
all_env.append(V1EnvVar(key="PARAM_TYPE_STR", value=param_type_str_repr))
|
866
|
-
|
867
|
-
|
868
|
-
|
920
|
+
|
921
|
+
return_type_str_repr = str(return_type)
|
922
|
+
if module_path != "__main__" and "__main__." in return_type_str_repr:
|
923
|
+
return_type_str_repr = return_type_str_repr.replace(
|
924
|
+
"__main__.", f"{module_path}."
|
925
|
+
)
|
926
|
+
print(
|
927
|
+
f"[DEBUG Decorator] Adjusted return type string: {return_type_str_repr}"
|
928
|
+
)
|
929
|
+
|
930
|
+
all_env.append(V1EnvVar(key="RETURN_TYPE_STR", value=return_type_str_repr))
|
869
931
|
all_env.append(V1EnvVar(key="IS_STREAM_MESSAGE", value=str(is_stream_message)))
|
870
932
|
if content_type and hasattr(content_type, "__name__"):
|
933
|
+
content_type_name = None
|
871
934
|
# Check if content_type is a class before accessing __name__
|
872
935
|
if isinstance(content_type, type):
|
936
|
+
content_type_name = content_type.__name__
|
873
937
|
all_env.append(
|
874
|
-
V1EnvVar(key="CONTENT_TYPE_NAME", value=
|
938
|
+
V1EnvVar(key="CONTENT_TYPE_NAME", value=content_type_name)
|
875
939
|
)
|
876
940
|
else:
|
877
941
|
# Handle unresolved types / typevars if needed
|
878
942
|
print(
|
879
943
|
f"Warning: Content type '{content_type}' is not a class, cannot get name."
|
880
944
|
)
|
881
|
-
#
|
945
|
+
# Use the calculated module_path for MODULE_NAME
|
882
946
|
all_env.append(
|
883
|
-
V1EnvVar(
|
884
|
-
|
947
|
+
V1EnvVar(
|
948
|
+
key="MODULE_NAME", value=module_path
|
949
|
+
) # module_path is guaranteed to be a string here (calculated or fallback)
|
950
|
+
)
|
951
|
+
print(f"[DEBUG Decorator] Set MODULE_NAME to: {module_path}")
|
885
952
|
|
886
953
|
# Add PYTHONPATH
|
887
954
|
pythonpath_value = CONTAINER_CODE_DIR
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|