kailash 0.8.3__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 (84) 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/__init__.py +17 -0
  37. kailash/nodes/ai/a2a.py +1914 -43
  38. kailash/nodes/ai/a2a_backup.py +1807 -0
  39. kailash/nodes/ai/hybrid_search.py +972 -0
  40. kailash/nodes/ai/semantic_memory.py +558 -0
  41. kailash/nodes/ai/streaming_analytics.py +947 -0
  42. kailash/nodes/base.py +545 -0
  43. kailash/nodes/edge/__init__.py +36 -0
  44. kailash/nodes/edge/base.py +240 -0
  45. kailash/nodes/edge/cloud_node.py +710 -0
  46. kailash/nodes/edge/coordination.py +239 -0
  47. kailash/nodes/edge/docker_node.py +825 -0
  48. kailash/nodes/edge/edge_data.py +582 -0
  49. kailash/nodes/edge/edge_migration_node.py +392 -0
  50. kailash/nodes/edge/edge_monitoring_node.py +421 -0
  51. kailash/nodes/edge/edge_state.py +673 -0
  52. kailash/nodes/edge/edge_warming_node.py +393 -0
  53. kailash/nodes/edge/kubernetes_node.py +652 -0
  54. kailash/nodes/edge/platform_node.py +766 -0
  55. kailash/nodes/edge/resource_analyzer_node.py +378 -0
  56. kailash/nodes/edge/resource_optimizer_node.py +501 -0
  57. kailash/nodes/edge/resource_scaler_node.py +397 -0
  58. kailash/nodes/ports.py +676 -0
  59. kailash/runtime/local.py +344 -1
  60. kailash/runtime/validation/__init__.py +20 -0
  61. kailash/runtime/validation/connection_context.py +119 -0
  62. kailash/runtime/validation/enhanced_error_formatter.py +202 -0
  63. kailash/runtime/validation/error_categorizer.py +164 -0
  64. kailash/runtime/validation/metrics.py +380 -0
  65. kailash/runtime/validation/performance.py +615 -0
  66. kailash/runtime/validation/suggestion_engine.py +212 -0
  67. kailash/testing/fixtures.py +2 -2
  68. kailash/workflow/builder.py +234 -8
  69. kailash/workflow/contracts.py +418 -0
  70. kailash/workflow/edge_infrastructure.py +369 -0
  71. kailash/workflow/migration.py +3 -3
  72. kailash/workflow/type_inference.py +669 -0
  73. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/METADATA +44 -27
  74. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/RECORD +78 -28
  75. kailash/nexus/__init__.py +0 -21
  76. kailash/nexus/cli/__init__.py +0 -5
  77. kailash/nexus/cli/__main__.py +0 -6
  78. kailash/nexus/cli/main.py +0 -176
  79. kailash/nexus/factory.py +0 -413
  80. kailash/nexus/gateway.py +0 -545
  81. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
  82. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
  83. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
  84. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
@@ -396,6 +396,16 @@ class ConnectionCircuitBreaker(Generic[T]):
396
396
  if listener in self._listeners:
397
397
  self._listeners.remove(listener)
398
398
 
399
+ @property
400
+ def success_count(self) -> int:
401
+ """Get number of successful calls."""
402
+ return self.metrics.successful_calls
403
+
404
+ @property
405
+ def failure_count(self) -> int:
406
+ """Get number of failed calls."""
407
+ return self.metrics.failed_calls
408
+
399
409
  def get_status(self) -> Dict[str, Any]:
400
410
  """Get current circuit breaker status."""
401
411
  return {
@@ -435,10 +445,16 @@ class ConnectionCircuitBreaker(Generic[T]):
435
445
  class CircuitBreakerManager:
436
446
  """Manages multiple circuit breakers for different resources."""
437
447
 
438
- def __init__(self):
448
+ def __init__(self, performance_monitor=None):
439
449
  """Initialize circuit breaker manager."""
440
450
  self._breakers: Dict[str, ConnectionCircuitBreaker] = {}
441
451
  self._default_config = CircuitBreakerConfig()
452
+ self._performance_monitor = performance_monitor
453
+ self._patterns = {
454
+ "database": CircuitBreakerConfig(failure_threshold=5, recovery_timeout=60),
455
+ "api": CircuitBreakerConfig(failure_threshold=3, recovery_timeout=30),
456
+ "cache": CircuitBreakerConfig(failure_threshold=2, recovery_timeout=15),
457
+ }
442
458
 
443
459
  def get_or_create(
444
460
  self, name: str, config: Optional[CircuitBreakerConfig] = None
@@ -450,6 +466,60 @@ class CircuitBreakerManager:
450
466
  )
451
467
  return self._breakers[name]
452
468
 
469
+ def create_circuit_breaker(
470
+ self, name: str, config: Optional[CircuitBreakerConfig] = None, pattern: Optional[str] = None
471
+ ) -> ConnectionCircuitBreaker:
472
+ """Create a new circuit breaker with optional pattern-based configuration."""
473
+ if pattern and pattern in self._patterns:
474
+ config = config or self._patterns[pattern]
475
+ return self.get_or_create(name, config)
476
+
477
+ async def execute_with_circuit_breaker(
478
+ self, name: str, func: Callable, fallback: Optional[Callable] = None
479
+ ):
480
+ """Execute a function with circuit breaker protection."""
481
+ cb = self.get_or_create(name)
482
+ try:
483
+ result = await cb.call(func)
484
+ return result
485
+ except CircuitBreakerError:
486
+ if fallback:
487
+ if asyncio.iscoroutinefunction(fallback):
488
+ return await fallback()
489
+ else:
490
+ return fallback()
491
+ raise
492
+
493
+ def get_circuit_breaker(self, name: str) -> Optional[ConnectionCircuitBreaker]:
494
+ """Get an existing circuit breaker by name."""
495
+ return self._breakers.get(name)
496
+
497
+ def get_all_circuit_states(self) -> Dict[str, Dict[str, Any]]:
498
+ """Get the state of all circuit breakers."""
499
+ return {name: cb.get_status() for name, cb in self._breakers.items()}
500
+
501
+ def force_open_circuit_breaker(self, name: str) -> bool:
502
+ """Manually open a circuit breaker."""
503
+ cb = self._breakers.get(name)
504
+ if cb:
505
+ asyncio.create_task(cb.force_open("Manual override"))
506
+ return True
507
+ return False
508
+
509
+ def reset_circuit_breaker(self, name: str) -> bool:
510
+ """Reset a circuit breaker to closed state."""
511
+ cb = self._breakers.get(name)
512
+ if cb:
513
+ asyncio.create_task(cb.reset())
514
+ return True
515
+ return False
516
+
517
+ def add_global_callback(self, callback: Callable):
518
+ """Add a global callback for circuit breaker state changes."""
519
+ # For now, add to all existing breakers
520
+ for cb in self._breakers.values():
521
+ cb.add_listener(callback)
522
+
453
523
  def get_all_status(self) -> Dict[str, Dict[str, Any]]:
454
524
  """Get status of all circuit breakers."""
455
525
  return {name: breaker.get_status() for name, breaker in self._breakers.items()}
@@ -576,3 +576,175 @@ async def quick_health_check(service_name: str) -> bool:
576
576
  return result.is_healthy if result else False
577
577
  except Exception:
578
578
  return False
579
+
580
+
581
+ @dataclass
582
+ class HealthSummary:
583
+ """Health summary for all checks."""
584
+
585
+ total_checks: int
586
+ healthy_checks: int
587
+ degraded_checks: int
588
+ unhealthy_checks: int
589
+ overall_status: HealthStatus
590
+ details: List[HealthCheckResult]
591
+
592
+
593
+ class HealthCheckManager:
594
+ """Manager for orchestrating multiple health checks with configuration."""
595
+
596
+ def __init__(self, config: Dict[str, Any]):
597
+ """Initialize health check manager with configuration."""
598
+ self.config = config
599
+ self.enabled = config.get("enabled", True)
600
+ self.default_interval = config.get("default_interval", 30.0)
601
+ self.parallel_checks = config.get("parallel_checks", True)
602
+ self.max_concurrent_checks = config.get("max_concurrent_checks", 10)
603
+
604
+ self.health_checks: Dict[str, HealthCheck] = {}
605
+ self.check_intervals: Dict[str, float] = {}
606
+ self.last_results: Dict[str, HealthCheckResult] = {}
607
+ self.history: Dict[str, List[HealthCheckResult]] = {}
608
+ self.status_change_callbacks: List[Callable] = []
609
+ self._running = False
610
+
611
+ def register_health_check(self, health_check: HealthCheck, interval: float = None):
612
+ """Register a health check with optional interval."""
613
+ check_name = health_check.check_name
614
+ self.health_checks[check_name] = health_check
615
+ self.check_intervals[check_name] = interval or self.default_interval
616
+ self.history[check_name] = []
617
+
618
+ async def run_health_check(self, check_name: str) -> HealthCheckResult:
619
+ """Run a specific health check."""
620
+ if check_name not in self.health_checks:
621
+ raise ValueError(f"Health check '{check_name}' not found")
622
+
623
+ health_check = self.health_checks[check_name]
624
+ result = await health_check.check_health()
625
+
626
+ # Store result
627
+ self.last_results[check_name] = result
628
+ self.history[check_name].append(result)
629
+
630
+ # Check for status changes
631
+ await self._check_status_change(check_name, result)
632
+
633
+ return result
634
+
635
+ async def run_all_health_checks(self) -> List[HealthCheckResult]:
636
+ """Run all registered health checks."""
637
+ if not self.health_checks:
638
+ return []
639
+
640
+ if self.parallel_checks:
641
+ # Run checks in parallel
642
+ tasks = [
643
+ self.run_health_check(check_name)
644
+ for check_name in self.health_checks.keys()
645
+ ]
646
+
647
+ # Limit concurrency
648
+ semaphore = asyncio.Semaphore(self.max_concurrent_checks)
649
+
650
+ async def run_with_semaphore(task):
651
+ async with semaphore:
652
+ return await task
653
+
654
+ results = await asyncio.gather(*[
655
+ run_with_semaphore(task) for task in tasks
656
+ ])
657
+ else:
658
+ # Run checks sequentially
659
+ results = []
660
+ for check_name in self.health_checks.keys():
661
+ result = await self.run_health_check(check_name)
662
+ results.append(result)
663
+
664
+ return results
665
+
666
+ async def get_health_summary(self) -> HealthSummary:
667
+ """Get summary of all health checks."""
668
+ results = await self.run_all_health_checks()
669
+
670
+ healthy_count = sum(1 for r in results if r.status == HealthStatus.HEALTHY)
671
+ degraded_count = sum(1 for r in results if r.status == HealthStatus.DEGRADED)
672
+ unhealthy_count = sum(1 for r in results if r.status == HealthStatus.UNHEALTHY)
673
+
674
+ # Determine overall status
675
+ if unhealthy_count > 0:
676
+ overall_status = HealthStatus.UNHEALTHY
677
+ elif degraded_count > 0:
678
+ overall_status = HealthStatus.DEGRADED
679
+ elif healthy_count > 0:
680
+ overall_status = HealthStatus.HEALTHY
681
+ else:
682
+ overall_status = HealthStatus.UNKNOWN
683
+
684
+ return HealthSummary(
685
+ total_checks=len(results),
686
+ healthy_checks=healthy_count,
687
+ degraded_checks=degraded_count,
688
+ unhealthy_checks=unhealthy_count,
689
+ overall_status=overall_status,
690
+ details=results
691
+ )
692
+
693
+ def add_status_change_callback(self, callback: Callable):
694
+ """Add callback for status changes."""
695
+ self.status_change_callbacks.append(callback)
696
+
697
+ def get_health_history(self, check_name: str, limit: int = None) -> List[HealthCheckResult]:
698
+ """Get health check history for a specific check."""
699
+ history = self.history.get(check_name, [])
700
+ if limit:
701
+ return history[-limit:]
702
+ return history
703
+
704
+ async def _check_status_change(self, check_name: str, result: HealthCheckResult):
705
+ """Check if status has changed and notify callbacks."""
706
+ if check_name in self.last_results:
707
+ previous = self.last_results[check_name]
708
+ if previous.status != result.status:
709
+ # Status changed, notify callbacks
710
+ for callback in self.status_change_callbacks:
711
+ try:
712
+ await callback(check_name, result)
713
+ except Exception as e:
714
+ logger.error(f"Error in status change callback: {e}")
715
+
716
+ async def shutdown(self):
717
+ """Shutdown the health check manager."""
718
+ self._running = False
719
+ # Any cleanup logic here
720
+
721
+
722
+ # Add convenience functions for registering health checks
723
+ async def register_database_health_check(name: str, database_node, interval: float = 30.0):
724
+ """Register a database health check with global manager."""
725
+ health_check = DatabaseHealthCheck(name, database_node)
726
+ manager = get_health_manager()
727
+ if hasattr(manager, 'register_health_check'):
728
+ manager.register_health_check(health_check, interval)
729
+ else:
730
+ manager.register_check(name, health_check)
731
+
732
+
733
+ async def register_memory_health_check(name: str, warning_threshold: float = 80.0,
734
+ critical_threshold: float = 95.0, interval: float = 30.0):
735
+ """Register a memory health check with global manager."""
736
+ # This would need MemoryHealthCheck implementation
737
+ pass
738
+
739
+
740
+ async def register_custom_health_check(name: str, check_func: Callable,
741
+ interval: float = 30.0, timeout: float = 10.0):
742
+ """Register a custom health check with global manager."""
743
+ # This would need CustomHealthCheck implementation
744
+ pass
745
+
746
+
747
+ def get_health_manager():
748
+ """Get the global health manager instance."""
749
+ # Return a default HealthMonitor for now
750
+ return HealthMonitor()
@@ -785,6 +785,39 @@ class ComplianceRouter:
785
785
  """Get recent compliance decisions from audit log."""
786
786
  return self.audit_log[-limit:]
787
787
 
788
+ def is_compliant_location(
789
+ self,
790
+ location: "EdgeLocation",
791
+ data_class: DataClassification,
792
+ required_zones: List[str],
793
+ ) -> bool:
794
+ """Check if a location is compliant for given data class and zones.
795
+
796
+ Args:
797
+ location: Edge location to check
798
+ data_class: Classification of the data
799
+ required_zones: Required compliance zones
800
+
801
+ Returns:
802
+ True if location is compliant
803
+ """
804
+ # Avoid circular import
805
+ from kailash.edge.location import EdgeRegion
806
+
807
+ # Check if location has all required compliance zones
808
+ location_zones = [z.value for z in location.compliance_zones]
809
+
810
+ # For GDPR compliance, PII/EU_PERSONAL data must be in EU regions or GDPR-compliant zones
811
+ if "gdpr" in required_zones and data_class in [
812
+ DataClassification.PII,
813
+ DataClassification.EU_PERSONAL,
814
+ ]:
815
+ # Check if location has GDPR compliance zone
816
+ return "gdpr" in location_zones
817
+
818
+ # For other cases, check if location has the required zones
819
+ return all(zone in location_zones for zone in required_zones)
820
+
788
821
  def get_compliance_summary(self) -> Dict[str, Any]:
789
822
  """Get summary of compliance decisions and performance."""
790
823
  if not self.audit_log: