kailash 0.8.4__py3-none-any.whl → 0.8.5__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.
Files changed (79) hide show
  1. kailash/__init__.py +1 -7
  2. kailash/cli/__init__.py +11 -1
  3. kailash/cli/validation_audit.py +570 -0
  4. kailash/core/actors/supervisor.py +1 -1
  5. kailash/core/resilience/circuit_breaker.py +71 -1
  6. kailash/core/resilience/health_monitor.py +172 -0
  7. kailash/edge/compliance.py +33 -0
  8. kailash/edge/consistency.py +609 -0
  9. kailash/edge/coordination/__init__.py +30 -0
  10. kailash/edge/coordination/global_ordering.py +355 -0
  11. kailash/edge/coordination/leader_election.py +217 -0
  12. kailash/edge/coordination/partition_detector.py +296 -0
  13. kailash/edge/coordination/raft.py +485 -0
  14. kailash/edge/discovery.py +63 -1
  15. kailash/edge/migration/__init__.py +19 -0
  16. kailash/edge/migration/edge_migrator.py +832 -0
  17. kailash/edge/monitoring/__init__.py +21 -0
  18. kailash/edge/monitoring/edge_monitor.py +736 -0
  19. kailash/edge/prediction/__init__.py +10 -0
  20. kailash/edge/prediction/predictive_warmer.py +591 -0
  21. kailash/edge/resource/__init__.py +102 -0
  22. kailash/edge/resource/cloud_integration.py +796 -0
  23. kailash/edge/resource/cost_optimizer.py +949 -0
  24. kailash/edge/resource/docker_integration.py +919 -0
  25. kailash/edge/resource/kubernetes_integration.py +893 -0
  26. kailash/edge/resource/platform_integration.py +913 -0
  27. kailash/edge/resource/predictive_scaler.py +959 -0
  28. kailash/edge/resource/resource_analyzer.py +824 -0
  29. kailash/edge/resource/resource_pools.py +610 -0
  30. kailash/integrations/dataflow_edge.py +261 -0
  31. kailash/mcp_server/registry_integration.py +1 -1
  32. kailash/monitoring/__init__.py +18 -0
  33. kailash/monitoring/alerts.py +646 -0
  34. kailash/monitoring/metrics.py +677 -0
  35. kailash/nodes/__init__.py +2 -0
  36. kailash/nodes/ai/semantic_memory.py +2 -2
  37. kailash/nodes/base.py +545 -0
  38. kailash/nodes/edge/__init__.py +36 -0
  39. kailash/nodes/edge/base.py +240 -0
  40. kailash/nodes/edge/cloud_node.py +710 -0
  41. kailash/nodes/edge/coordination.py +239 -0
  42. kailash/nodes/edge/docker_node.py +825 -0
  43. kailash/nodes/edge/edge_data.py +582 -0
  44. kailash/nodes/edge/edge_migration_node.py +392 -0
  45. kailash/nodes/edge/edge_monitoring_node.py +421 -0
  46. kailash/nodes/edge/edge_state.py +673 -0
  47. kailash/nodes/edge/edge_warming_node.py +393 -0
  48. kailash/nodes/edge/kubernetes_node.py +652 -0
  49. kailash/nodes/edge/platform_node.py +766 -0
  50. kailash/nodes/edge/resource_analyzer_node.py +378 -0
  51. kailash/nodes/edge/resource_optimizer_node.py +501 -0
  52. kailash/nodes/edge/resource_scaler_node.py +397 -0
  53. kailash/nodes/ports.py +676 -0
  54. kailash/runtime/local.py +344 -1
  55. kailash/runtime/validation/__init__.py +20 -0
  56. kailash/runtime/validation/connection_context.py +119 -0
  57. kailash/runtime/validation/enhanced_error_formatter.py +202 -0
  58. kailash/runtime/validation/error_categorizer.py +164 -0
  59. kailash/runtime/validation/metrics.py +380 -0
  60. kailash/runtime/validation/performance.py +615 -0
  61. kailash/runtime/validation/suggestion_engine.py +212 -0
  62. kailash/testing/fixtures.py +2 -2
  63. kailash/workflow/builder.py +230 -4
  64. kailash/workflow/contracts.py +418 -0
  65. kailash/workflow/edge_infrastructure.py +369 -0
  66. kailash/workflow/migration.py +3 -3
  67. kailash/workflow/type_inference.py +669 -0
  68. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/METADATA +43 -27
  69. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/RECORD +73 -27
  70. kailash/nexus/__init__.py +0 -21
  71. kailash/nexus/cli/__init__.py +0 -5
  72. kailash/nexus/cli/__main__.py +0 -6
  73. kailash/nexus/cli/main.py +0 -176
  74. kailash/nexus/factory.py +0 -413
  75. kailash/nexus/gateway.py +0 -545
  76. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
  77. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
  78. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
  79. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
kailash/nodes/ports.py ADDED
@@ -0,0 +1,676 @@
1
+ """
2
+ Type-safe input/output port system for Kailash nodes.
3
+
4
+ This module provides a type-safe port system that enables:
5
+ - Compile-time type checking with IDE support
6
+ - Runtime type validation
7
+ - Clear port declarations in node definitions
8
+ - Automatic type inference for connections
9
+ - Better developer experience with autocomplete
10
+
11
+ Design Goals:
12
+ 1. Type safety: Catch type mismatches at design time
13
+ 2. IDE support: Full autocomplete and type hints
14
+ 3. Runtime validation: Enforce types during execution
15
+ 4. Backward compatibility: Works with existing nodes
16
+ 5. Performance: Minimal runtime overhead
17
+
18
+ Example Usage:
19
+ class MyNode(TypedNode):
20
+ # Input ports
21
+ text_input = InputPort[str]("text_input", description="Text to process")
22
+ count = InputPort[int]("count", default=1, description="Number of iterations")
23
+
24
+ # Output ports
25
+ result = OutputPort[str]("result", description="Processed text")
26
+ metadata = OutputPort[Dict[str, Any]]("metadata", description="Processing metadata")
27
+
28
+ def run(self, **kwargs) -> Dict[str, Any]:
29
+ text = self.text_input.get()
30
+ count = self.count.get()
31
+
32
+ # Process...
33
+ processed = text * count
34
+
35
+ return {
36
+ self.result.name: processed,
37
+ self.metadata.name: {"length": len(processed)}
38
+ }
39
+ """
40
+
41
+ import logging
42
+ from abc import ABC, abstractmethod
43
+ from dataclasses import dataclass, field
44
+ from typing import (
45
+ Any,
46
+ Dict,
47
+ Generic,
48
+ List,
49
+ Optional,
50
+ Type,
51
+ TypeVar,
52
+ Union,
53
+ get_args,
54
+ get_origin,
55
+ get_type_hints,
56
+ )
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ # Type variable for generic port types
61
+ T = TypeVar("T")
62
+
63
+
64
+ @dataclass
65
+ class PortMetadata:
66
+ """Metadata for input/output ports."""
67
+
68
+ name: str
69
+ description: str = ""
70
+ required: bool = True
71
+ default: Any = None
72
+ constraints: Dict[str, Any] = field(default_factory=dict)
73
+ examples: List[Any] = field(default_factory=list)
74
+
75
+ def to_dict(self) -> Dict[str, Any]:
76
+ """Convert to dictionary for serialization."""
77
+ return {
78
+ "name": self.name,
79
+ "description": self.description,
80
+ "required": self.required,
81
+ "default": self.default,
82
+ "constraints": self.constraints,
83
+ "examples": self.examples,
84
+ }
85
+
86
+
87
+ class Port(Generic[T], ABC):
88
+ """Base class for typed input/output ports."""
89
+
90
+ def __init__(
91
+ self,
92
+ name: str,
93
+ description: str = "",
94
+ required: bool = True,
95
+ default: Optional[T] = None,
96
+ constraints: Optional[Dict[str, Any]] = None,
97
+ examples: Optional[List[T]] = None,
98
+ ):
99
+ """Initialize a port.
100
+
101
+ Args:
102
+ name: Port name (should match parameter name)
103
+ description: Human-readable description
104
+ required: Whether this port is required
105
+ default: Default value if not connected
106
+ constraints: Additional validation constraints
107
+ examples: Example values for documentation
108
+ """
109
+ self.name = name
110
+ self.metadata = PortMetadata(
111
+ name=name,
112
+ description=description,
113
+ required=required,
114
+ default=default,
115
+ constraints=constraints or {},
116
+ examples=examples or [],
117
+ )
118
+ self._type_hint: Optional[Type[T]] = None
119
+ self._value: Optional[T] = None
120
+ self._node_instance: Optional[Any] = None
121
+
122
+ # Schedule type hint extraction to happen after __orig_class__ is set
123
+ self._extract_type_hint()
124
+
125
+ def _extract_type_hint(self):
126
+ """Extract type hint from __orig_class__ after instance creation."""
127
+ # This will be called again from a delayed context when __orig_class__ is available
128
+ if hasattr(self, "__orig_class__"):
129
+ args = get_args(self.__orig_class__)
130
+ if args:
131
+ self._type_hint = args[0]
132
+ return True
133
+ return False
134
+
135
+ def __set_name__(self, owner: Type, name: str) -> None:
136
+ """Called when port is assigned to a class attribute."""
137
+ if self.name != name:
138
+ logger.warning(
139
+ f"Port name '{self.name}' doesn't match attribute name '{name}'. Using '{name}'."
140
+ )
141
+ self.name = name
142
+ self.metadata.name = name
143
+
144
+ # Extract type hint from class annotations
145
+ if hasattr(owner, "__annotations__") and name in owner.__annotations__:
146
+ annotation = owner.__annotations__[name]
147
+ # Extract the type argument from Port[T]
148
+ if hasattr(annotation, "__args__") and annotation.__args__:
149
+ self._type_hint = annotation.__args__[0]
150
+ elif hasattr(annotation, "__origin__"):
151
+ # Handle Generic types
152
+ origin = get_origin(annotation)
153
+ args = get_args(annotation)
154
+ if origin and args:
155
+ self._type_hint = args[0]
156
+
157
+ # If no type hint found, try to extract from the port instance itself
158
+ if self._type_hint is None and hasattr(self, "__orig_class__"):
159
+ args = get_args(self.__orig_class__)
160
+ if args:
161
+ self._type_hint = args[0]
162
+
163
+ def __get__(self, instance: Any, owner: Type = None) -> "Port[T]":
164
+ """Descriptor protocol - return port instance bound to node."""
165
+ if instance is None:
166
+ return self
167
+
168
+ # Cache bound ports per instance to maintain state
169
+ cache_attr = f"_bound_port_{self.name}_{id(self)}"
170
+ if hasattr(instance, cache_attr):
171
+ return getattr(instance, cache_attr)
172
+
173
+ # Create a copy bound to this instance
174
+ if isinstance(self, OutputPort):
175
+ bound_port = self.__class__(
176
+ name=self.name,
177
+ description=self.metadata.description,
178
+ constraints=self.metadata.constraints,
179
+ examples=self.metadata.examples,
180
+ )
181
+ else:
182
+ bound_port = self.__class__(
183
+ name=self.name,
184
+ description=self.metadata.description,
185
+ required=self.metadata.required,
186
+ default=self.metadata.default,
187
+ constraints=self.metadata.constraints,
188
+ examples=self.metadata.examples,
189
+ )
190
+ bound_port._type_hint = self._type_hint
191
+ bound_port._node_instance = instance
192
+
193
+ # Cache the bound port
194
+ setattr(instance, cache_attr, bound_port)
195
+ return bound_port
196
+
197
+ def __set__(self, instance: Any, value: T) -> None:
198
+ """Descriptor protocol - set port value."""
199
+ self._value = value
200
+ self._node_instance = instance
201
+
202
+ @property
203
+ def type_hint(self) -> Optional[Type[T]]:
204
+ """Get the type hint for this port."""
205
+ return self._type_hint
206
+
207
+ def get_type_name(self) -> str:
208
+ """Get human-readable type name."""
209
+ # Lazy type hint extraction
210
+ if self._type_hint is None:
211
+ self._extract_type_hint()
212
+
213
+ if self._type_hint:
214
+ if hasattr(self._type_hint, "__name__"):
215
+ return self._type_hint.__name__
216
+ else:
217
+ return str(self._type_hint)
218
+ return "Any"
219
+
220
+ def validate_type(self, value: Any) -> bool:
221
+ """Validate that value matches port type."""
222
+ # Lazy type hint extraction - try again if not set yet
223
+ if self._type_hint is None:
224
+ self._extract_type_hint()
225
+
226
+ if self._type_hint is None:
227
+ return True # No type constraint
228
+
229
+ # Handle None values
230
+ if value is None:
231
+ return not self.metadata.required
232
+
233
+ # Basic type checking
234
+ try:
235
+ if isinstance(self._type_hint, type):
236
+ return isinstance(value, self._type_hint)
237
+ else:
238
+ # Handle complex types like Union, Optional, etc.
239
+ return self._check_complex_type(value, self._type_hint)
240
+ except Exception as e:
241
+ logger.warning(f"Type validation error for port '{self.name}': {e}")
242
+ return False
243
+
244
+ def _check_complex_type(self, value: Any, type_hint: Type) -> bool:
245
+ """Check complex types like Union, Optional, List[str], etc."""
246
+ origin = get_origin(type_hint)
247
+ args = get_args(type_hint)
248
+
249
+ if origin is Union:
250
+ # Check if value matches any of the union types
251
+ return any(self._check_complex_type(value, arg) for arg in args)
252
+ elif origin is list and args:
253
+ # Check List[T] - all elements must be of type T
254
+ if not isinstance(value, list):
255
+ return False
256
+ return all(self._check_complex_type(item, args[0]) for item in value)
257
+ elif origin is dict and len(args) >= 2:
258
+ # Check Dict[K, V]
259
+ if not isinstance(value, dict):
260
+ return False
261
+ return all(
262
+ self._check_complex_type(k, args[0]) for k in value.keys()
263
+ ) and all(self._check_complex_type(v, args[1]) for v in value.values())
264
+ else:
265
+ # Fallback to isinstance for basic types
266
+ try:
267
+ return isinstance(value, type_hint)
268
+ except TypeError:
269
+ # Some types can't be used with isinstance
270
+ return True
271
+
272
+ def validate_constraints(self, value: Any) -> tuple[bool, Optional[str]]:
273
+ """Validate value against port constraints.
274
+
275
+ Returns:
276
+ Tuple of (is_valid, error_message)
277
+ """
278
+ if not self.metadata.constraints:
279
+ return True, None
280
+
281
+ for constraint, constraint_value in self.metadata.constraints.items():
282
+ if constraint == "min_length" and hasattr(value, "__len__"):
283
+ if len(value) < constraint_value:
284
+ return (
285
+ False,
286
+ f"Value length {len(value)} is less than minimum {constraint_value}",
287
+ )
288
+ elif constraint == "max_length" and hasattr(value, "__len__"):
289
+ if len(value) > constraint_value:
290
+ return (
291
+ False,
292
+ f"Value length {len(value)} is greater than maximum {constraint_value}",
293
+ )
294
+ elif constraint == "min_value" and isinstance(value, (int, float)):
295
+ if value < constraint_value:
296
+ return (
297
+ False,
298
+ f"Value {value} is less than minimum {constraint_value}",
299
+ )
300
+ elif constraint == "max_value" and isinstance(value, (int, float)):
301
+ if value > constraint_value:
302
+ return (
303
+ False,
304
+ f"Value {value} is greater than maximum {constraint_value}",
305
+ )
306
+ elif constraint == "pattern" and isinstance(value, str):
307
+ import re
308
+
309
+ if not re.match(constraint_value, value):
310
+ return (
311
+ False,
312
+ f"Value '{value}' does not match pattern '{constraint_value}'",
313
+ )
314
+
315
+ return True, None
316
+
317
+ @abstractmethod
318
+ def get(self) -> T:
319
+ """Get the value from this port."""
320
+ pass
321
+
322
+ def to_dict(self) -> Dict[str, Any]:
323
+ """Convert port to dictionary for serialization."""
324
+ return {
325
+ "name": self.name,
326
+ "type": self.get_type_name(),
327
+ "metadata": self.metadata.to_dict(),
328
+ "port_type": self.__class__.__name__,
329
+ }
330
+
331
+
332
+ class InputPort(Port[T]):
333
+ """Input port for receiving data into a node."""
334
+
335
+ def __init__(
336
+ self,
337
+ name: str,
338
+ description: str = "",
339
+ required: bool = True,
340
+ default: Optional[T] = None,
341
+ constraints: Optional[Dict[str, Any]] = None,
342
+ examples: Optional[List[T]] = None,
343
+ ):
344
+ """Initialize an input port.
345
+
346
+ Args:
347
+ name: Port name
348
+ description: Description of what this port accepts
349
+ required: Whether this port must be connected or have a value
350
+ default: Default value if not connected
351
+ constraints: Validation constraints (min_length, max_length, min_value, max_value, pattern)
352
+ examples: Example values for documentation
353
+ """
354
+ super().__init__(name, description, required, default, constraints, examples)
355
+
356
+ def get(self) -> T:
357
+ """Get the value from this input port.
358
+
359
+ Returns:
360
+ The input value, or default if not set
361
+
362
+ Raises:
363
+ ValueError: If port is required but no value is available
364
+ """
365
+ if self._value is not None:
366
+ return self._value
367
+ elif self.metadata.default is not None:
368
+ return self.metadata.default
369
+ elif not self.metadata.required:
370
+ return None
371
+ else:
372
+ raise ValueError(f"Required input port '{self.name}' has no value")
373
+
374
+ def set(self, value: T) -> None:
375
+ """Set the value for this input port.
376
+
377
+ Args:
378
+ value: Value to set
379
+
380
+ Raises:
381
+ TypeError: If value doesn't match port type
382
+ ValueError: If value doesn't meet constraints
383
+ """
384
+ # Type validation
385
+ if not self.validate_type(value):
386
+ raise TypeError(
387
+ f"Input port '{self.name}' expects {self.get_type_name()}, got {type(value).__name__}"
388
+ )
389
+
390
+ # Constraint validation
391
+ is_valid, error = self.validate_constraints(value)
392
+ if not is_valid:
393
+ raise ValueError(f"Input port '{self.name}' constraint violation: {error}")
394
+
395
+ self._value = value
396
+
397
+ def is_connected(self) -> bool:
398
+ """Check if this input port has a value (connected or default)."""
399
+ return self._value is not None or self.metadata.default is not None
400
+
401
+
402
+ class OutputPort(Port[T]):
403
+ """Output port for sending data from a node."""
404
+
405
+ def __init__(
406
+ self,
407
+ name: str,
408
+ description: str = "",
409
+ constraints: Optional[Dict[str, Any]] = None,
410
+ examples: Optional[List[T]] = None,
411
+ ):
412
+ """Initialize an output port.
413
+
414
+ Args:
415
+ name: Port name
416
+ description: Description of what this port produces
417
+ constraints: Validation constraints for output values
418
+ examples: Example output values for documentation
419
+ """
420
+ # Output ports are never required (they're always produced by the node)
421
+ super().__init__(
422
+ name,
423
+ description,
424
+ required=False,
425
+ default=None,
426
+ constraints=constraints,
427
+ examples=examples,
428
+ )
429
+
430
+ def get(self) -> T:
431
+ """Get the value from this output port.
432
+
433
+ Returns:
434
+ The output value
435
+
436
+ Raises:
437
+ ValueError: If no value has been set
438
+ """
439
+ if self._value is None:
440
+ raise ValueError(f"Output port '{self.name}' has no value")
441
+ return self._value
442
+
443
+ def set(self, value: T) -> None:
444
+ """Set the value for this output port.
445
+
446
+ Args:
447
+ value: Value to set
448
+
449
+ Raises:
450
+ TypeError: If value doesn't match port type
451
+ ValueError: If value doesn't meet constraints
452
+ """
453
+ # Type validation
454
+ if not self.validate_type(value):
455
+ raise TypeError(
456
+ f"Output port '{self.name}' expects {self.get_type_name()}, got {type(value).__name__}"
457
+ )
458
+
459
+ # Constraint validation
460
+ is_valid, error = self.validate_constraints(value)
461
+ if not is_valid:
462
+ raise ValueError(f"Output port '{self.name}' constraint violation: {error}")
463
+
464
+ self._value = value
465
+
466
+ def has_value(self) -> bool:
467
+ """Check if this output port has been set."""
468
+ return self._value is not None
469
+
470
+
471
+ class PortRegistry:
472
+ """Registry for managing ports in a node class."""
473
+
474
+ def __init__(self, node_class: Type):
475
+ """Initialize port registry for a node class.
476
+
477
+ Args:
478
+ node_class: The node class to analyze
479
+ """
480
+ self.node_class = node_class
481
+ self._input_ports: Dict[str, InputPort] = {}
482
+ self._output_ports: Dict[str, OutputPort] = {}
483
+ self._scan_ports()
484
+
485
+ def _scan_ports(self) -> None:
486
+ """Scan the node class for port definitions."""
487
+ for name, attr in self.node_class.__dict__.items():
488
+ if isinstance(attr, InputPort):
489
+ self._input_ports[name] = attr
490
+ elif isinstance(attr, OutputPort):
491
+ self._output_ports[name] = attr
492
+
493
+ @property
494
+ def input_ports(self) -> Dict[str, InputPort]:
495
+ """Get all input ports."""
496
+ return self._input_ports.copy()
497
+
498
+ @property
499
+ def output_ports(self) -> Dict[str, OutputPort]:
500
+ """Get all output ports."""
501
+ return self._output_ports.copy()
502
+
503
+ def get_port_schema(self) -> Dict[str, Any]:
504
+ """Get JSON schema for all ports."""
505
+ return {
506
+ "input_ports": {
507
+ name: port.to_dict() for name, port in self._input_ports.items()
508
+ },
509
+ "output_ports": {
510
+ name: port.to_dict() for name, port in self._output_ports.items()
511
+ },
512
+ }
513
+
514
+ def validate_input_types(self, inputs: Dict[str, Any]) -> List[str]:
515
+ """Validate input types against port definitions.
516
+
517
+ Args:
518
+ inputs: Input values to validate
519
+
520
+ Returns:
521
+ List of validation errors (empty if all valid)
522
+ """
523
+ errors = []
524
+
525
+ # Check required inputs
526
+ for name, port in self._input_ports.items():
527
+ if (
528
+ port.metadata.required
529
+ and name not in inputs
530
+ and port.metadata.default is None
531
+ ):
532
+ errors.append(f"Required input port '{name}' is missing")
533
+ continue
534
+
535
+ if name in inputs:
536
+ value = inputs[name]
537
+
538
+ # Type validation
539
+ if not port.validate_type(value):
540
+ errors.append(
541
+ f"Input port '{name}' expects {port.get_type_name()}, got {type(value).__name__}"
542
+ )
543
+
544
+ # Constraint validation
545
+ is_valid, error = port.validate_constraints(value)
546
+ if not is_valid:
547
+ errors.append(f"Input port '{name}': {error}")
548
+
549
+ return errors
550
+
551
+ def validate_output_types(self, outputs: Dict[str, Any]) -> List[str]:
552
+ """Validate output types against port definitions.
553
+
554
+ Args:
555
+ outputs: Output values to validate
556
+
557
+ Returns:
558
+ List of validation errors (empty if all valid)
559
+ """
560
+ errors = []
561
+
562
+ for name, value in outputs.items():
563
+ if name in self._output_ports:
564
+ port = self._output_ports[name]
565
+
566
+ # Type validation
567
+ if not port.validate_type(value):
568
+ errors.append(
569
+ f"Output port '{name}' expects {port.get_type_name()}, got {type(value).__name__}"
570
+ )
571
+
572
+ # Constraint validation
573
+ is_valid, error = port.validate_constraints(value)
574
+ if not is_valid:
575
+ errors.append(f"Output port '{name}': {error}")
576
+
577
+ return errors
578
+
579
+
580
+ def get_port_registry(node_class: Type) -> PortRegistry:
581
+ """Get port registry for a node class.
582
+
583
+ Args:
584
+ node_class: Node class to analyze
585
+
586
+ Returns:
587
+ PortRegistry instance
588
+ """
589
+ if not hasattr(node_class, "_port_registry"):
590
+ node_class._port_registry = PortRegistry(node_class)
591
+ return node_class._port_registry
592
+
593
+
594
+ # Convenience type aliases for common port types
595
+ def StringPort(name: str, **kwargs) -> InputPort[str]:
596
+ """Create a string input port."""
597
+ port = InputPort[str](name, **kwargs)
598
+ port._type_hint = str
599
+ return port
600
+
601
+
602
+ def IntPort(name: str, **kwargs) -> InputPort[int]:
603
+ """Create an integer input port."""
604
+ port = InputPort[int](name, **kwargs)
605
+ port._type_hint = int
606
+ return port
607
+
608
+
609
+ def FloatPort(name: str, **kwargs) -> InputPort[float]:
610
+ """Create a float input port."""
611
+ port = InputPort[float](name, **kwargs)
612
+ port._type_hint = float
613
+ return port
614
+
615
+
616
+ def BoolPort(name: str, **kwargs) -> InputPort[bool]:
617
+ """Create a boolean input port."""
618
+ port = InputPort[bool](name, **kwargs)
619
+ port._type_hint = bool
620
+ return port
621
+
622
+
623
+ def ListPort(name: str, **kwargs) -> InputPort[List[Any]]:
624
+ """Create a list input port."""
625
+ port = InputPort[List[Any]](name, **kwargs)
626
+ port._type_hint = List[Any]
627
+ return port
628
+
629
+
630
+ def DictPort(name: str, **kwargs) -> InputPort[Dict[str, Any]]:
631
+ """Create a dict input port."""
632
+ port = InputPort[Dict[str, Any]](name, **kwargs)
633
+ port._type_hint = Dict[str, Any]
634
+ return port
635
+
636
+
637
+ def StringOutput(name: str, **kwargs) -> OutputPort[str]:
638
+ """Create a string output port."""
639
+ port = OutputPort[str](name, **kwargs)
640
+ port._type_hint = str
641
+ return port
642
+
643
+
644
+ def IntOutput(name: str, **kwargs) -> OutputPort[int]:
645
+ """Create an integer output port."""
646
+ port = OutputPort[int](name, **kwargs)
647
+ port._type_hint = int
648
+ return port
649
+
650
+
651
+ def FloatOutput(name: str, **kwargs) -> OutputPort[float]:
652
+ """Create a float output port."""
653
+ port = OutputPort[float](name, **kwargs)
654
+ port._type_hint = float
655
+ return port
656
+
657
+
658
+ def BoolOutput(name: str, **kwargs) -> OutputPort[bool]:
659
+ """Create a boolean output port."""
660
+ port = OutputPort[bool](name, **kwargs)
661
+ port._type_hint = bool
662
+ return port
663
+
664
+
665
+ def ListOutput(name: str, **kwargs) -> OutputPort[List[Any]]:
666
+ """Create a list output port."""
667
+ port = OutputPort[List[Any]](name, **kwargs)
668
+ port._type_hint = List[Any]
669
+ return port
670
+
671
+
672
+ def DictOutput(name: str, **kwargs) -> OutputPort[Dict[str, Any]]:
673
+ """Create a dict output port."""
674
+ port = OutputPort[Dict[str, Any]](name, **kwargs)
675
+ port._type_hint = Dict[str, Any]
676
+ return port