nebu 0.1.113__tar.gz → 0.1.115__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.113/src/nebu.egg-info → nebu-0.1.115}/PKG-INFO +1 -1
- {nebu-0.1.113 → nebu-0.1.115}/pyproject.toml +1 -1
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/decorate.py +57 -4
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/processor.py +93 -58
- {nebu-0.1.113 → nebu-0.1.115/src/nebu.egg-info}/PKG-INFO +1 -1
- {nebu-0.1.113 → nebu-0.1.115}/LICENSE +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/README.md +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/setup.cfg +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/__init__.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/auth.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/builders/builder.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/builders/models.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/cache.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/config.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/containers/container.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/containers/models.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/data.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/errors.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/logging.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/meta.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/namespaces/models.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/namespaces/namespace.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/orign.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/consumer.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/consumer_process_worker.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/default.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/processors/models.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/redis/models.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu/services/service.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu.egg-info/SOURCES.txt +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu.egg-info/dependency_links.txt +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu.egg-info/requires.txt +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/src/nebu.egg-info/top_level.txt +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/tests/test_bucket.py +0 -0
- {nebu-0.1.113 → nebu-0.1.115}/tests/test_containers.py +0 -0
@@ -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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
#
|
136
|
-
if self.
|
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
|
156
|
+
f"Inferred output_model_cls {output_type_candidate.__name__} from generic arguments."
|
149
157
|
)
|
150
|
-
self.
|
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
|
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
|
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.
|
343
|
-
print(">>> type(self.
|
344
|
-
print(
|
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.
|
349
|
-
and isinstance(self.
|
350
|
-
and issubclass(self.
|
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
|
-
):
|
355
|
+
):
|
353
356
|
print(f">>> raw_content: {raw_content}")
|
354
357
|
try:
|
355
|
-
|
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
|
-
|
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 {
|
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
|
-
|
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
|
-
|
416
|
-
|
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
|
)
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|