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.
Files changed (31) hide show
  1. {nebu-0.1.49/src/nebu.egg-info → nebu-0.1.52}/PKG-INFO +1 -1
  2. {nebu-0.1.49 → nebu-0.1.52}/pyproject.toml +1 -1
  3. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/consumer.py +9 -5
  4. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/decorate.py +118 -51
  5. {nebu-0.1.49 → nebu-0.1.52/src/nebu.egg-info}/PKG-INFO +1 -1
  6. {nebu-0.1.49 → nebu-0.1.52}/LICENSE +0 -0
  7. {nebu-0.1.49 → nebu-0.1.52}/README.md +0 -0
  8. {nebu-0.1.49 → nebu-0.1.52}/setup.cfg +0 -0
  9. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/__init__.py +0 -0
  10. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/auth.py +0 -0
  11. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/builders/builder.py +0 -0
  12. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/builders/models.py +0 -0
  13. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/cache.py +0 -0
  14. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/config.py +0 -0
  15. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/container.py +0 -0
  16. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/decorator.py +0 -0
  17. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/models.py +0 -0
  18. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/containers/server.py +0 -0
  19. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/data.py +0 -0
  20. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/meta.py +0 -0
  21. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/default.py +0 -0
  22. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/models.py +0 -0
  23. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/processor.py +0 -0
  24. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/processors/remote.py +0 -0
  25. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/redis/models.py +0 -0
  26. {nebu-0.1.49 → nebu-0.1.52}/src/nebu/services/service.py +0 -0
  27. {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/SOURCES.txt +0 -0
  28. {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/dependency_links.txt +0 -0
  29. {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/requires.txt +0 -0
  30. {nebu-0.1.49 → nebu-0.1.52}/src/nebu.egg-info/top_level.txt +0 -0
  31. {nebu-0.1.49 → nebu-0.1.52}/tests/test_containers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.49
3
+ Version: 0.1.52
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nebu"
3
- version = "0.1.49"
3
+ version = "0.1.52"
4
4
  description = "A globally distributed container runtime"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10.14"
@@ -6,7 +6,7 @@ import socket
6
6
  import sys
7
7
  import time
8
8
  import traceback
9
- import types # Added for ModuleType
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 not payload_str or not isinstance(payload_str, str):
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
- # Simplified xreadgroup call - redis-py handles encoding now
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 json # Add json 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 # Add urlparse import
16
+ from urllib.parse import urlparse
21
17
 
22
- import dill # Add dill import
23
- import requests # Add requests import
24
- from botocore.exceptions import ClientError # 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 # Import get_user_profile
28
- from nebu.config import GlobalConfig # Add this import
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 # 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
- # print(f"[DEBUG Helper] is_jupyter_notebook: Exception occurred: {e}") # Reduce verbosity
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(node, def_type)
168
- and hasattr(node, "name")
169
- and node.name == def_name
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>-<uuid>/
505
- unique_suffix = f"{processor_name}-{uuid.uuid4()}"
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=False,
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
- origin_src, arg_srcs = obj_source
609
- if origin_src and isinstance(origin_src, str):
610
- all_env.append(
611
- V1EnvVar(key=f"{env_key_base}_SOURCE", value=origin_src)
612
- )
613
- for j, arg_src in enumerate(arg_srcs):
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}_ARG_{j}_SOURCE",
618
- value=arg_src,
617
+ key=f"{env_key_base}_SOURCE", value=origin_src
619
618
  )
620
619
  )
621
- # Handle nested tuples if necessary, or keep it simple
622
- # print(f"[DEBUG Decorator] Added tuple source to env for included obj: {obj_name_str}")
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
- # Basic info needed by consumer to find and run the function
830
- all_env.append(V1EnvVar(key="FUNCTION_NAME", value=processor_name))
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
- module_path = rel_func_path.replace(os.sep, ".")
834
- if module_path.endswith(".py"):
835
- module_path = module_path[:-3]
836
- # Handle __init__.py -> treat as package name
837
- if module_path.endswith(".__init__"):
838
- module_path = module_path[: -len(".__init__")]
839
- elif module_path == "__init__": # Top-level __init__.py
840
- module_path = "" # Or handle differently? Let's assume it means import '.'? Maybe error?
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
- all_env.append(
867
- V1EnvVar(key="RETURN_TYPE_STR", value=return_type_str_repr)
868
- ) # Use repr
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=content_type.__name__)
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
- # MODULE_NAME might be less reliable now, depends on where func is defined relative to project root
945
+ # Use the calculated module_path for MODULE_NAME
882
946
  all_env.append(
883
- V1EnvVar(key="MODULE_NAME", value=func.__module__)
884
- ) # Keep for potential debugging/info
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.49
3
+ Version: 0.1.52
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
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