nebu 0.1.113__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,35 +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
- 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__"):
137
149
  type_args = get_args(self.__orig_class__) # type: ignore
138
- print(">>> type_args: ", type_args)
139
150
  if len(type_args) == 2:
140
151
  output_type_candidate = type_args[1]
141
- print(">>> output_type_candidate: ", output_type_candidate)
142
- # Check if it looks like a Pydantic model class
143
152
  if isinstance(output_type_candidate, type) and issubclass(
144
153
  output_type_candidate, BaseModel
145
154
  ):
146
- print(">>> output_type_candidate is a Pydantic model class")
147
155
  logger.debug(
148
- f"Inferred OutputType {output_type_candidate.__name__} from generic arguments."
156
+ f"Inferred output_model_cls {output_type_candidate.__name__} from generic arguments."
149
157
  )
150
- self.schema_ = output_type_candidate
158
+ self.output_model_cls = output_type_candidate
151
159
  else:
152
- print(">>> output_type_candidate is not a Pydantic model class")
153
160
  logger.debug(
154
161
  f"Second generic argument {output_type_candidate} is not a Pydantic BaseModel. "
155
- "Cannot infer OutputType."
162
+ "Cannot infer output_model_cls."
156
163
  )
157
164
  else:
158
- print(
159
- "Could not infer OutputType from generic arguments: wrong number of type args found "
160
- f"(expected 2, got {len(type_args) if type_args else 0})."
161
- )
162
165
  logger.debug(
163
- "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 "
164
167
  f"(expected 2, got {len(type_args) if type_args else 0})."
165
168
  )
166
169
 
@@ -199,7 +202,6 @@ class Processor(Generic[InputType, OutputType]):
199
202
  processor_request = V1ProcessorRequest(
200
203
  metadata=metadata,
201
204
  container=container,
202
- schema_=schema_,
203
205
  common_schema=common_schema,
204
206
  min_replicas=min_replicas,
205
207
  max_replicas=max_replicas,
@@ -224,7 +226,6 @@ class Processor(Generic[InputType, OutputType]):
224
226
 
225
227
  update_processor = V1UpdateProcessor(
226
228
  container=container,
227
- schema_=schema_,
228
229
  common_schema=common_schema,
229
230
  min_replicas=min_replicas,
230
231
  max_replicas=max_replicas,
@@ -339,38 +340,37 @@ class Processor(Generic[InputType, OutputType]):
339
340
 
340
341
  # Attempt to parse into OutputType if conditions are met
341
342
  print(f">>> wait: {wait}")
342
- print(f">>> self.schema_: {self.schema_}")
343
- print(">>> type(self.schema_): ", type(self.schema_))
344
- 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
+ )
345
348
  print(f">>> isinstance(raw_content, dict): {isinstance(raw_content, dict)}")
346
349
  if (
347
350
  wait
348
- and self.schema_
349
- and isinstance(self.schema_, type)
350
- 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
351
354
  and isinstance(raw_content, dict)
352
- ): # Check if raw_content is a dict
355
+ ):
353
356
  print(f">>> raw_content: {raw_content}")
354
357
  try:
355
- # self.schema_ is assumed to be the Pydantic model class for OutputType
356
- # Parse raw_content instead of the full response
357
- parsed_model = self.schema_.model_validate(raw_content)
358
+ parsed_model = self.output_model_cls.model_validate(raw_content)
358
359
  print(f">>> parsed_model: {parsed_model}")
359
- # Cast to OutputType to satisfy the linter with generics
360
360
  parsed_output: OutputType = cast(OutputType, parsed_model)
361
361
  print(f">>> parsed_output: {parsed_output}")
362
362
  return parsed_output
363
- except (
364
- Exception
365
- ) as e: # Consider pydantic.ValidationError for more specific handling
363
+ except Exception as e:
366
364
  print(f">>> error: {e}")
367
- 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
+ )
368
368
  logger.error(
369
- 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}. "
370
370
  f"Error: {e}. Returning raw JSON response."
371
371
  )
372
- # Fallback to returning the raw JSON response
373
372
  return raw_content
373
+ # Fallback logic using self.schema_ has been removed.
374
374
 
375
375
  return raw_content
376
376
 
@@ -399,6 +399,8 @@ class Processor(Generic[InputType, OutputType]):
399
399
  namespace: Optional[str] = None,
400
400
  config: Optional[GlobalConfig] = None,
401
401
  api_key: Optional[str] = None,
402
+ input_model_cls: Optional[type[BaseModel]] = None,
403
+ output_model_cls: Optional[type[BaseModel]] = None,
402
404
  ):
403
405
  """
404
406
  Get a Processor from the remote server.
@@ -410,27 +412,60 @@ class Processor(Generic[InputType, OutputType]):
410
412
  raise ValueError("Processor not found")
411
413
  processor_v1 = processors[0]
412
414
 
413
- 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.
414
466
  out.processor = processor_v1
415
- out.config = config or GlobalConfig.read()
416
- if not out.config:
417
- raise ValueError("No config found")
418
- out.current_server = out.config.get_current_server_config()
419
- if not out.current_server:
420
- raise ValueError("No server config found")
421
- out.api_key = api_key or out.current_server.api_key
422
- out.orign_host = out.current_server.server
423
- out.processors_url = f"{out.orign_host}/v1/processors"
424
- out.name = name
425
- out.namespace = namespace
426
-
427
- # Set specific fields from the processor
428
- out.container = processor_v1.container
429
- out.schema_ = processor_v1.schema_
430
- out.common_schema = processor_v1.common_schema
431
- out.min_replicas = processor_v1.min_replicas
432
- out.max_replicas = processor_v1.max_replicas
433
- 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__
434
469
 
435
470
  return out
436
471
 
@@ -547,7 +582,7 @@ class Processor(Generic[InputType, OutputType]):
547
582
 
548
583
  # Send health check and wait for response
549
584
  response = self.send(
550
- data=health_check_data, # type: ignore
585
+ data=health_check_data, # type: ignore[arg-type]
551
586
  wait=True,
552
587
  timeout=30.0, # Short timeout for individual health check
553
588
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.113
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=pYxFx2ZzoaeTsXqvimVywzK7x5_lCSDqMRQMNtDhAPA,23006
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.113.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
- nebu-0.1.113.dist-info/METADATA,sha256=9snz2J4nG4boO5fIiUYiwVMERXY-lM6Sb5Qnwp1w4Uc,1798
26
- nebu-0.1.113.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
27
- nebu-0.1.113.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
28
- nebu-0.1.113.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