nebu 0.1.114__py3-none-any.whl → 0.1.115__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.
@@ -1194,6 +1194,52 @@ def processor(
1194
1194
 
1195
1195
  # --- Final Setup ---
1196
1196
  logger.debug("Decorator: Preparing final Processor object...")
1197
+
1198
+ # Determine ResolvedInputType for Processor Generic
1199
+ ResolvedInputType: type[BaseModel] = (
1200
+ BaseModel # Default to BaseModel to satisfy generic bound
1201
+ )
1202
+ if is_stream_message:
1203
+ if (
1204
+ content_type
1205
+ and isinstance(content_type, type)
1206
+ and issubclass(content_type, BaseModel)
1207
+ ):
1208
+ ResolvedInputType = content_type
1209
+ else:
1210
+ logger.warning(
1211
+ f"Decorator: Message type hint found, but ContentType '{content_type!s}' is not a valid Pydantic Model. Defaulting InputType to BaseModel."
1212
+ )
1213
+ # ResolvedInputType remains BaseModel (default)
1214
+ elif (
1215
+ param_type
1216
+ and isinstance(param_type, type)
1217
+ and issubclass(param_type, BaseModel)
1218
+ ):
1219
+ ResolvedInputType = param_type # Function takes the data model directly
1220
+ else:
1221
+ logger.warning(
1222
+ f"Decorator: Parameter type '{param_type!s}' is not a valid Pydantic Model. Defaulting InputType to BaseModel."
1223
+ )
1224
+ # ResolvedInputType remains BaseModel (default)
1225
+
1226
+ ResolvedOutputType: type[BaseModel] = BaseModel # Default to BaseModel
1227
+ if (
1228
+ return_type
1229
+ and isinstance(return_type, type)
1230
+ and issubclass(return_type, BaseModel)
1231
+ ):
1232
+ ResolvedOutputType = return_type
1233
+ elif return_type is not None: # It was something, but not a BaseModel subclass
1234
+ logger.warning(
1235
+ f"Decorator: Return type '{return_type!s}' is not a valid Pydantic Model. Defaulting OutputType to BaseModel."
1236
+ )
1237
+ # Else (return_type is None), ResolvedOutputType remains BaseModel
1238
+
1239
+ logger.debug(
1240
+ f"Decorator: Resolved Generic Types for Processor: InputType={ResolvedInputType.__name__}, OutputType={ResolvedOutputType.__name__}"
1241
+ )
1242
+
1197
1243
  metadata = V1ResourceMetaRequest(
1198
1244
  name=processor_name, namespace=effective_namespace, labels=labels
1199
1245
  )
@@ -1225,7 +1271,7 @@ def processor(
1225
1271
  final_command = "\n".join(all_commands)
1226
1272
 
1227
1273
  logger.debug(
1228
- f"Decorator: Final container command:\n-------\n{final_command}\n-------"
1274
+ f"Decorator: Final container command:\\n-------\\n{final_command}\\n-------"
1229
1275
  )
1230
1276
 
1231
1277
  container_request = V1ContainerRequest(
@@ -1259,21 +1305,28 @@ def processor(
1259
1305
  else:
1260
1306
  logger.debug(f" {env_var.key}: {value_str}")
1261
1307
 
1262
- processor_instance = Processor(
1308
+ # Create the generically typed Processor instance
1309
+ _processor_instance = Processor[ResolvedInputType, ResolvedOutputType](
1263
1310
  name=processor_name,
1264
1311
  namespace=effective_namespace,
1265
1312
  labels=labels,
1266
1313
  container=container_request,
1267
- schema_=None, # Schema info might be derived differently now if needed
1314
+ input_model_cls=ResolvedInputType,
1315
+ output_model_cls=ResolvedOutputType,
1268
1316
  common_schema=None,
1269
1317
  min_replicas=min_replicas,
1270
1318
  max_replicas=max_replicas,
1271
1319
  scale_config=scale,
1320
+ config=effective_config,
1321
+ api_key=api_key,
1272
1322
  no_delete=no_delete,
1273
1323
  wait_for_healthy=wait_for_healthy,
1274
1324
  )
1325
+ # Type hint for the variable. The instance itself IS correctly typed with specific models.
1326
+ processor_instance: Processor[BaseModel, BaseModel] = _processor_instance
1327
+
1275
1328
  logger.debug(
1276
- f"Decorator: Processor instance '{processor_name}' created successfully."
1329
+ f"Decorator: Processor instance '{processor_name}' created successfully with generic types."
1277
1330
  )
1278
1331
  # Store original func for potential local invocation/testing? Keep for now.
1279
1332
  # TODO: Add original_func to Processor model definition if this is desired
@@ -2,7 +2,17 @@ import json
2
2
  import threading
3
3
  import time
4
4
  import uuid
5
- from typing import Any, Dict, Generic, List, Optional, TypeVar, cast, get_args
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ Generic,
9
+ List,
10
+ Optional,
11
+ TypeVar,
12
+ cast,
13
+ get_args,
14
+ get_origin,
15
+ )
6
16
 
7
17
  import requests
8
18
  from pydantic import BaseModel
@@ -101,7 +111,8 @@ class Processor(Generic[InputType, OutputType]):
101
111
  namespace: Optional[str] = None,
102
112
  labels: Optional[Dict[str, str]] = None,
103
113
  container: Optional[V1ContainerRequest] = None,
104
- schema_: Optional[Any] = None,
114
+ input_model_cls: Optional[type[BaseModel]] = None,
115
+ output_model_cls: Optional[type[BaseModel]] = None,
105
116
  common_schema: Optional[str] = None,
106
117
  min_replicas: Optional[int] = None,
107
118
  max_replicas: Optional[int] = None,
@@ -124,7 +135,8 @@ class Processor(Generic[InputType, OutputType]):
124
135
  self.namespace = namespace
125
136
  self.labels = labels
126
137
  self.container = container
127
- self.schema_ = schema_
138
+ self.input_model_cls = input_model_cls
139
+ self.output_model_cls = output_model_cls
128
140
  self.common_schema = common_schema
129
141
  self.min_replicas = min_replicas
130
142
  self.max_replicas = max_replicas
@@ -132,37 +144,26 @@ class Processor(Generic[InputType, OutputType]):
132
144
  self.processors_url = f"{self.orign_host}/v1/processors"
133
145
  self._log_thread: Optional[threading.Thread] = None
134
146
 
135
- # Attempt to infer OutputType if schema_ is not provided
136
- print(">>> self.schema_: ", self.schema_)
137
- print("self.__dict__: ", self.__dict__)
138
- if self.schema_ is None and hasattr(self, "__orig_class__"):
147
+ # Infer OutputType Pydantic class if output_model_cls is not provided
148
+ if self.output_model_cls is None and hasattr(self, "__orig_class__"):
139
149
  type_args = get_args(self.__orig_class__) # type: ignore
140
- print(">>> type_args: ", type_args)
141
150
  if len(type_args) == 2:
142
151
  output_type_candidate = type_args[1]
143
- print(">>> output_type_candidate: ", output_type_candidate)
144
- # Check if it looks like a Pydantic model class
145
152
  if isinstance(output_type_candidate, type) and issubclass(
146
153
  output_type_candidate, BaseModel
147
154
  ):
148
- print(">>> output_type_candidate is a Pydantic model class")
149
155
  logger.debug(
150
- f"Inferred OutputType {output_type_candidate.__name__} from generic arguments."
156
+ f"Inferred output_model_cls {output_type_candidate.__name__} from generic arguments."
151
157
  )
152
- self.schema_ = output_type_candidate
158
+ self.output_model_cls = output_type_candidate
153
159
  else:
154
- print(">>> output_type_candidate is not a Pydantic model class")
155
160
  logger.debug(
156
161
  f"Second generic argument {output_type_candidate} is not a Pydantic BaseModel. "
157
- "Cannot infer OutputType."
162
+ "Cannot infer output_model_cls."
158
163
  )
159
164
  else:
160
- print(
161
- "Could not infer OutputType from generic arguments: wrong number of type args found "
162
- f"(expected 2, got {len(type_args) if type_args else 0})."
163
- )
164
165
  logger.debug(
165
- "Could not infer OutputType from generic arguments: wrong number of type args found "
166
+ "Could not infer output_model_cls from generic arguments: wrong number of type args found "
166
167
  f"(expected 2, got {len(type_args) if type_args else 0})."
167
168
  )
168
169
 
@@ -201,7 +202,6 @@ class Processor(Generic[InputType, OutputType]):
201
202
  processor_request = V1ProcessorRequest(
202
203
  metadata=metadata,
203
204
  container=container,
204
- schema_=schema_,
205
205
  common_schema=common_schema,
206
206
  min_replicas=min_replicas,
207
207
  max_replicas=max_replicas,
@@ -226,7 +226,6 @@ class Processor(Generic[InputType, OutputType]):
226
226
 
227
227
  update_processor = V1UpdateProcessor(
228
228
  container=container,
229
- schema_=schema_,
230
229
  common_schema=common_schema,
231
230
  min_replicas=min_replicas,
232
231
  max_replicas=max_replicas,
@@ -341,38 +340,37 @@ class Processor(Generic[InputType, OutputType]):
341
340
 
342
341
  # Attempt to parse into OutputType if conditions are met
343
342
  print(f">>> wait: {wait}")
344
- print(f">>> self.schema_: {self.schema_}")
345
- print(">>> type(self.schema_): ", type(self.schema_))
346
- print(f">>> isinstance(self.schema_, type): {isinstance(self.schema_, type)}")
343
+ print(f">>> self.output_model_cls: {self.output_model_cls}")
344
+ print(">>> type(self.output_model_cls): ", type(self.output_model_cls))
345
+ print(
346
+ f">>> isinstance(self.output_model_cls, type): {isinstance(self.output_model_cls, type)}"
347
+ )
347
348
  print(f">>> isinstance(raw_content, dict): {isinstance(raw_content, dict)}")
348
349
  if (
349
350
  wait
350
- and self.schema_
351
- and isinstance(self.schema_, type)
352
- and issubclass(self.schema_, BaseModel) # type: ignore
351
+ and self.output_model_cls
352
+ and isinstance(self.output_model_cls, type)
353
+ and issubclass(self.output_model_cls, BaseModel) # type: ignore
353
354
  and isinstance(raw_content, dict)
354
- ): # Check if raw_content is a dict
355
+ ):
355
356
  print(f">>> raw_content: {raw_content}")
356
357
  try:
357
- # self.schema_ is assumed to be the Pydantic model class for OutputType
358
- # Parse raw_content instead of the full response
359
- parsed_model = self.schema_.model_validate(raw_content)
358
+ parsed_model = self.output_model_cls.model_validate(raw_content)
360
359
  print(f">>> parsed_model: {parsed_model}")
361
- # Cast to OutputType to satisfy the linter with generics
362
360
  parsed_output: OutputType = cast(OutputType, parsed_model)
363
361
  print(f">>> parsed_output: {parsed_output}")
364
362
  return parsed_output
365
- except (
366
- Exception
367
- ) as e: # Consider pydantic.ValidationError for more specific handling
363
+ except Exception as e:
368
364
  print(f">>> error: {e}")
369
- schema_name = getattr(self.schema_, "__name__", str(self.schema_))
365
+ model_name = getattr(
366
+ self.output_model_cls, "__name__", str(self.output_model_cls)
367
+ )
370
368
  logger.error(
371
- f"Processor {processor_name}: Failed to parse 'content' field into output type {schema_name}. "
369
+ f"Processor {processor_name}: Failed to parse 'content' field into output type {model_name}. "
372
370
  f"Error: {e}. Returning raw JSON response."
373
371
  )
374
- # Fallback to returning the raw JSON response
375
372
  return raw_content
373
+ # Fallback logic using self.schema_ has been removed.
376
374
 
377
375
  return raw_content
378
376
 
@@ -401,6 +399,8 @@ class Processor(Generic[InputType, OutputType]):
401
399
  namespace: Optional[str] = None,
402
400
  config: Optional[GlobalConfig] = None,
403
401
  api_key: Optional[str] = None,
402
+ input_model_cls: Optional[type[BaseModel]] = None,
403
+ output_model_cls: Optional[type[BaseModel]] = None,
404
404
  ):
405
405
  """
406
406
  Get a Processor from the remote server.
@@ -412,27 +412,60 @@ class Processor(Generic[InputType, OutputType]):
412
412
  raise ValueError("Processor not found")
413
413
  processor_v1 = processors[0]
414
414
 
415
- out = cls.__new__(cls)
415
+ # Try to infer Input/Output model classes if Processor.load is called as generic
416
+ # e.g., MyProcessor = Processor[MyInput, MyOutput]; MyProcessor.load(...)
417
+ loaded_input_model_cls: Optional[type[BaseModel]] = None
418
+ loaded_output_model_cls: Optional[type[BaseModel]] = None
419
+
420
+ # __orig_bases__ usually contains the generic version of the class if it was parameterized.
421
+ # We look for Processor[...] in the bases.
422
+ if hasattr(cls, "__orig_bases__"):
423
+ for base in cls.__orig_bases__: # type: ignore
424
+ if get_origin(base) is Processor:
425
+ type_args = get_args(base)
426
+ if len(type_args) == 2:
427
+ input_arg, output_arg = type_args
428
+ if isinstance(input_arg, type) and issubclass(
429
+ input_arg, BaseModel
430
+ ):
431
+ loaded_input_model_cls = input_arg
432
+ if isinstance(output_arg, type) and issubclass(
433
+ output_arg, BaseModel
434
+ ):
435
+ loaded_output_model_cls = output_arg
436
+ break # Found Processor generic base
437
+
438
+ # Determine final model classes, prioritizing overrides
439
+ final_input_model_cls = (
440
+ input_model_cls if input_model_cls is not None else loaded_input_model_cls
441
+ )
442
+ final_output_model_cls = (
443
+ output_model_cls
444
+ if output_model_cls is not None
445
+ else loaded_output_model_cls
446
+ )
447
+
448
+ out = cls.__new__(cls) # type: ignore
449
+ # If generic types were successfully inferred or overridden, pass them to init
450
+ # Otherwise, they will be None, and __init__ might try __orig_class__ if called on instance
451
+ out.__init__( # type: ignore
452
+ name=processor_v1.metadata.name, # Use name from fetched metadata
453
+ namespace=processor_v1.metadata.namespace, # Use namespace from fetched metadata
454
+ labels=processor_v1.metadata.labels, # Use labels from fetched metadata
455
+ container=processor_v1.container,
456
+ input_model_cls=final_input_model_cls, # Use determined input model
457
+ output_model_cls=final_output_model_cls, # Use determined output model
458
+ common_schema=processor_v1.common_schema,
459
+ min_replicas=processor_v1.min_replicas,
460
+ max_replicas=processor_v1.max_replicas,
461
+ scale_config=processor_v1.scale,
462
+ config=config, # Pass original config
463
+ api_key=api_key, # Pass original api_key
464
+ )
465
+ # The __init__ call above handles most setup. We store the fetched processor data.
416
466
  out.processor = processor_v1
417
- out.config = config or GlobalConfig.read()
418
- if not out.config:
419
- raise ValueError("No config found")
420
- out.current_server = out.config.get_current_server_config()
421
- if not out.current_server:
422
- raise ValueError("No server config found")
423
- out.api_key = api_key or out.current_server.api_key
424
- out.orign_host = out.current_server.server
425
- out.processors_url = f"{out.orign_host}/v1/processors"
426
- out.name = name
427
- out.namespace = namespace
428
-
429
- # Set specific fields from the processor
430
- out.container = processor_v1.container
431
- out.schema_ = processor_v1.schema_
432
- out.common_schema = processor_v1.common_schema
433
- out.min_replicas = processor_v1.min_replicas
434
- out.max_replicas = processor_v1.max_replicas
435
- out.scale_config = processor_v1.scale
467
+ # self.schema_ was removed, so no assignment for it here from processor_v1.schema_
468
+ # out.common_schema = processor_v1.common_schema # This is now set in __init__
436
469
 
437
470
  return out
438
471
 
@@ -549,7 +582,7 @@ class Processor(Generic[InputType, OutputType]):
549
582
 
550
583
  # Send health check and wait for response
551
584
  response = self.send(
552
- data=health_check_data, # type: ignore
585
+ data=health_check_data, # type: ignore[arg-type]
553
586
  wait=True,
554
587
  timeout=30.0, # Short timeout for individual health check
555
588
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.114
3
+ Version: 0.1.115
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -15,14 +15,14 @@ nebu/namespaces/models.py,sha256=EqUOpzhVBhvJw2P92ONDUbIgC31M9jMmcaG5vyOrsWg,497
15
15
  nebu/namespaces/namespace.py,sha256=oeZyGqsIGIrppyjif1ZONsdTmqRgd9oSLFE1BChXTTE,5247
16
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
- nebu/processors/decorate.py,sha256=aT6TT8QOOpl8HKo8kYdwmX6785_l9kSfoyUSPp4tkuY,58726
18
+ nebu/processors/decorate.py,sha256=wnos4w7htBUSOnNVyhY7NQgUuz9d2_-Kre5H99UiFpw,61089
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=Gh-RQ2EBvO2Ab91CJOKmzHjZiWG6TKvftTS6_Ogyctk,23104
21
+ nebu/processors/processor.py,sha256=fMXzodTe2qWnoCBry9nLsHLMEI6FxOOmcsR9DbuY_W0,24574
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.114.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
- nebu-0.1.114.dist-info/METADATA,sha256=6jG9Q52pjeBsLFyU5QwaZ7nSeL0bfGGYA9qaiIQvvTs,1798
26
- nebu-0.1.114.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
27
- nebu-0.1.114.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
28
- nebu-0.1.114.dist-info/RECORD,,
24
+ nebu-0.1.115.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
+ nebu-0.1.115.dist-info/METADATA,sha256=tud697lMA-FHCANFaBZKT20hTCyIxRMMQT3ZNdc0tQk,1798
26
+ nebu-0.1.115.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
27
+ nebu-0.1.115.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
28
+ nebu-0.1.115.dist-info/RECORD,,
File without changes