aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive Error Handling and Logging for Graph Storage
|
|
3
|
+
|
|
4
|
+
Provides structured error handling, logging, and exception types
|
|
5
|
+
for production-ready graph storage operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import traceback
|
|
11
|
+
from typing import Optional, Dict, Any, Callable
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
import functools
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GraphStoreError(Exception):
|
|
21
|
+
"""Base exception for graph store errors"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GraphStoreConnectionError(GraphStoreError):
|
|
25
|
+
"""Connection-related errors"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GraphStoreQueryError(GraphStoreError):
|
|
29
|
+
"""Query execution errors"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GraphStoreValidationError(GraphStoreError):
|
|
33
|
+
"""Data validation errors"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GraphStoreNotFoundError(GraphStoreError):
|
|
37
|
+
"""Entity/relation not found errors"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GraphStoreConflictError(GraphStoreError):
|
|
41
|
+
"""Conflict errors (duplicate IDs, etc.)"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class GraphStoreTimeoutError(GraphStoreError):
|
|
45
|
+
"""Operation timeout errors"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ErrorSeverity(str, Enum):
|
|
49
|
+
"""Error severity levels"""
|
|
50
|
+
|
|
51
|
+
LOW = "low"
|
|
52
|
+
MEDIUM = "medium"
|
|
53
|
+
HIGH = "high"
|
|
54
|
+
CRITICAL = "critical"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ErrorContext:
|
|
59
|
+
"""Context information for error reporting"""
|
|
60
|
+
|
|
61
|
+
operation: str
|
|
62
|
+
entity_id: Optional[str] = None
|
|
63
|
+
relation_id: Optional[str] = None
|
|
64
|
+
query: Optional[str] = None
|
|
65
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
66
|
+
timestamp: Optional[datetime] = None
|
|
67
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM
|
|
68
|
+
|
|
69
|
+
def __post_init__(self):
|
|
70
|
+
if self.timestamp is None:
|
|
71
|
+
self.timestamp = datetime.utcnow()
|
|
72
|
+
|
|
73
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
74
|
+
"""Convert to dictionary for logging"""
|
|
75
|
+
return {
|
|
76
|
+
"operation": self.operation,
|
|
77
|
+
"entity_id": self.entity_id,
|
|
78
|
+
"relation_id": self.relation_id,
|
|
79
|
+
"query": (self.query[:100] + "..." if self.query and len(self.query) > 100 else self.query),
|
|
80
|
+
"parameters": self.parameters,
|
|
81
|
+
"timestamp": self.timestamp.isoformat() if self.timestamp is not None else None,
|
|
82
|
+
"severity": self.severity.value,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ErrorHandler:
|
|
87
|
+
"""
|
|
88
|
+
Centralized error handling and logging
|
|
89
|
+
|
|
90
|
+
Provides structured error handling with context and logging.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
```python
|
|
94
|
+
handler = ErrorHandler()
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
await store.add_entity(entity)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
handler.handle_error(
|
|
100
|
+
e,
|
|
101
|
+
ErrorContext(
|
|
102
|
+
operation="add_entity",
|
|
103
|
+
entity_id=entity.id,
|
|
104
|
+
severity=ErrorSeverity.HIGH
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, log_level: int = logging.ERROR):
|
|
111
|
+
"""
|
|
112
|
+
Initialize error handler
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
log_level: Logging level for errors
|
|
116
|
+
"""
|
|
117
|
+
self.log_level = log_level
|
|
118
|
+
|
|
119
|
+
def handle_error(self, error: Exception, context: ErrorContext, reraise: bool = True) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Handle and log an error with context
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
error: Exception that occurred
|
|
125
|
+
context: Error context information
|
|
126
|
+
reraise: Whether to re-raise the exception
|
|
127
|
+
"""
|
|
128
|
+
# Map exception types
|
|
129
|
+
mapped_error = self._map_exception(error)
|
|
130
|
+
|
|
131
|
+
# Log error with context
|
|
132
|
+
self._log_error(mapped_error, context)
|
|
133
|
+
|
|
134
|
+
# Re-raise if requested
|
|
135
|
+
if reraise:
|
|
136
|
+
raise mapped_error from error
|
|
137
|
+
|
|
138
|
+
def _map_exception(self, error: Exception) -> Exception:
|
|
139
|
+
"""
|
|
140
|
+
Map generic exceptions to specific graph store exceptions
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
error: Original exception
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Mapped exception
|
|
147
|
+
"""
|
|
148
|
+
error_str = str(error).lower()
|
|
149
|
+
error_type = type(error).__name__
|
|
150
|
+
|
|
151
|
+
# Connection errors
|
|
152
|
+
if any(keyword in error_str for keyword in ["connection", "connect", "timeout", "network"]):
|
|
153
|
+
return GraphStoreConnectionError(str(error))
|
|
154
|
+
|
|
155
|
+
# Not found errors
|
|
156
|
+
if any(keyword in error_str for keyword in ["not found", "does not exist", "missing"]):
|
|
157
|
+
return GraphStoreNotFoundError(str(error))
|
|
158
|
+
|
|
159
|
+
# Conflict errors
|
|
160
|
+
if any(
|
|
161
|
+
keyword in error_str
|
|
162
|
+
for keyword in [
|
|
163
|
+
"duplicate",
|
|
164
|
+
"already exists",
|
|
165
|
+
"conflict",
|
|
166
|
+
"unique",
|
|
167
|
+
]
|
|
168
|
+
):
|
|
169
|
+
return GraphStoreConflictError(str(error))
|
|
170
|
+
|
|
171
|
+
# Validation errors
|
|
172
|
+
if any(keyword in error_str for keyword in ["invalid", "validation", "required", "missing"]):
|
|
173
|
+
return GraphStoreValidationError(str(error))
|
|
174
|
+
|
|
175
|
+
# Timeout errors
|
|
176
|
+
if "timeout" in error_str or "TimeoutError" in error_type:
|
|
177
|
+
return GraphStoreTimeoutError(str(error))
|
|
178
|
+
|
|
179
|
+
# Query errors
|
|
180
|
+
if any(keyword in error_str for keyword in ["syntax", "query", "sql", "execute"]):
|
|
181
|
+
return GraphStoreQueryError(str(error))
|
|
182
|
+
|
|
183
|
+
# Return original if no mapping
|
|
184
|
+
return error
|
|
185
|
+
|
|
186
|
+
def _log_error(self, error: Exception, context: ErrorContext) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Log error with structured context
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
error: Exception to log
|
|
192
|
+
context: Error context
|
|
193
|
+
"""
|
|
194
|
+
error_dict = {
|
|
195
|
+
"error_type": type(error).__name__,
|
|
196
|
+
"error_message": str(error),
|
|
197
|
+
"context": context.to_dict(),
|
|
198
|
+
"traceback": traceback.format_exc(),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Log based on severity
|
|
202
|
+
if context.severity == ErrorSeverity.CRITICAL:
|
|
203
|
+
logger.critical(f"CRITICAL: {error_dict}")
|
|
204
|
+
elif context.severity == ErrorSeverity.HIGH:
|
|
205
|
+
logger.error(f"HIGH: {error_dict}")
|
|
206
|
+
elif context.severity == ErrorSeverity.MEDIUM:
|
|
207
|
+
logger.warning(f"MEDIUM: {error_dict}")
|
|
208
|
+
else:
|
|
209
|
+
logger.info(f"LOW: {error_dict}")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def error_handler(
|
|
213
|
+
operation: str,
|
|
214
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
215
|
+
reraise: bool = True,
|
|
216
|
+
):
|
|
217
|
+
"""
|
|
218
|
+
Decorator for automatic error handling
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
operation: Operation name for context
|
|
222
|
+
severity: Error severity level
|
|
223
|
+
reraise: Whether to re-raise exceptions
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
```python
|
|
227
|
+
@error_handler("add_entity", severity=ErrorSeverity.HIGH)
|
|
228
|
+
async def add_entity(self, entity: Entity):
|
|
229
|
+
# Implementation
|
|
230
|
+
pass
|
|
231
|
+
```
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def decorator(func: Callable) -> Callable:
|
|
235
|
+
@functools.wraps(func)
|
|
236
|
+
async def wrapper(self, *args, **kwargs):
|
|
237
|
+
handler = ErrorHandler()
|
|
238
|
+
|
|
239
|
+
# Extract context from arguments
|
|
240
|
+
entity_id = None
|
|
241
|
+
relation_id = None
|
|
242
|
+
|
|
243
|
+
if args and hasattr(args[0], "id"):
|
|
244
|
+
entity_id = args[0].id
|
|
245
|
+
elif "entity_id" in kwargs:
|
|
246
|
+
entity_id = kwargs["entity_id"]
|
|
247
|
+
elif "relation_id" in kwargs:
|
|
248
|
+
relation_id = kwargs["relation_id"]
|
|
249
|
+
|
|
250
|
+
context = ErrorContext(
|
|
251
|
+
operation=operation,
|
|
252
|
+
entity_id=entity_id,
|
|
253
|
+
relation_id=relation_id,
|
|
254
|
+
severity=severity,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
return await func(self, *args, **kwargs)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
handler.handle_error(e, context, reraise=reraise)
|
|
261
|
+
|
|
262
|
+
return wrapper
|
|
263
|
+
|
|
264
|
+
return decorator
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class RetryHandler:
|
|
268
|
+
"""
|
|
269
|
+
Retry handler for transient errors
|
|
270
|
+
|
|
271
|
+
Implements exponential backoff retry logic for operations
|
|
272
|
+
that may fail due to transient issues.
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
```python
|
|
276
|
+
retry = RetryHandler(max_retries=3, base_delay=1.0)
|
|
277
|
+
|
|
278
|
+
result = await retry.execute(
|
|
279
|
+
lambda: store.get_entity("entity_1"),
|
|
280
|
+
retry_on=[GraphStoreConnectionError]
|
|
281
|
+
)
|
|
282
|
+
```
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
max_retries: int = 3,
|
|
288
|
+
base_delay: float = 1.0,
|
|
289
|
+
max_delay: float = 60.0,
|
|
290
|
+
exponential_base: float = 2.0,
|
|
291
|
+
):
|
|
292
|
+
"""
|
|
293
|
+
Initialize retry handler
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
max_retries: Maximum number of retry attempts
|
|
297
|
+
base_delay: Base delay in seconds
|
|
298
|
+
max_delay: Maximum delay in seconds
|
|
299
|
+
exponential_base: Exponential backoff multiplier
|
|
300
|
+
"""
|
|
301
|
+
self.max_retries = max_retries
|
|
302
|
+
self.base_delay = base_delay
|
|
303
|
+
self.max_delay = max_delay
|
|
304
|
+
self.exponential_base = exponential_base
|
|
305
|
+
|
|
306
|
+
async def execute(self, func: Callable, retry_on: Optional[list] = None, *args, **kwargs) -> Any:
|
|
307
|
+
"""
|
|
308
|
+
Execute function with retry logic
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
func: Async function to execute
|
|
312
|
+
retry_on: List of exception types to retry on (None = all)
|
|
313
|
+
*args: Function arguments
|
|
314
|
+
**kwargs: Function keyword arguments
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Function result
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
Last exception if all retries fail
|
|
321
|
+
"""
|
|
322
|
+
if retry_on is None:
|
|
323
|
+
retry_on = [Exception]
|
|
324
|
+
|
|
325
|
+
last_exception = None
|
|
326
|
+
|
|
327
|
+
for attempt in range(self.max_retries + 1):
|
|
328
|
+
try:
|
|
329
|
+
return await func(*args, **kwargs)
|
|
330
|
+
except Exception as e:
|
|
331
|
+
last_exception = e
|
|
332
|
+
|
|
333
|
+
# Check if we should retry
|
|
334
|
+
if not any(isinstance(e, exc_type) for exc_type in retry_on):
|
|
335
|
+
raise
|
|
336
|
+
|
|
337
|
+
# Don't retry on last attempt
|
|
338
|
+
if attempt >= self.max_retries:
|
|
339
|
+
break
|
|
340
|
+
|
|
341
|
+
# Calculate delay with exponential backoff
|
|
342
|
+
delay = min(
|
|
343
|
+
self.base_delay * (self.exponential_base**attempt),
|
|
344
|
+
self.max_delay,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
logger.warning(f"Retry attempt {attempt + 1}/{self.max_retries} after {delay:.1f}s: {e}")
|
|
348
|
+
|
|
349
|
+
await asyncio.sleep(delay)
|
|
350
|
+
|
|
351
|
+
# All retries exhausted
|
|
352
|
+
if last_exception is None:
|
|
353
|
+
raise RuntimeError("Retry logic failed but no exception was captured")
|
|
354
|
+
raise last_exception
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# Configure logging for graph storage
|
|
358
|
+
def configure_graph_storage_logging(level: int = logging.INFO, format_string: Optional[str] = None) -> None:
|
|
359
|
+
"""
|
|
360
|
+
Configure logging for graph storage modules
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
level: Logging level
|
|
364
|
+
format_string: Custom format string
|
|
365
|
+
"""
|
|
366
|
+
if format_string is None:
|
|
367
|
+
format_string = "%(asctime)s - %(name)s - %(levelname)s - " "%(message)s - [%(filename)s:%(lineno)d]"
|
|
368
|
+
|
|
369
|
+
handler = logging.StreamHandler()
|
|
370
|
+
handler.setFormatter(logging.Formatter(format_string))
|
|
371
|
+
|
|
372
|
+
# Configure graph storage logger
|
|
373
|
+
graph_logger = logging.getLogger("aiecs.infrastructure.graph_storage")
|
|
374
|
+
graph_logger.setLevel(level)
|
|
375
|
+
graph_logger.addHandler(handler)
|
|
376
|
+
|
|
377
|
+
logger.info(f"Graph storage logging configured at level {logging.getLevelName(level)}")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# Import asyncio for retry handler
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graceful Degradation for Graph Storage
|
|
3
|
+
|
|
4
|
+
Provides automatic fallback to in-memory storage when primary backend fails,
|
|
5
|
+
ensuring service continuity even during backend outages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional, Any, Dict
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
from aiecs.infrastructure.graph_storage import InMemoryGraphStore, GraphStore
|
|
14
|
+
from aiecs.infrastructure.graph_storage.error_handling import (
|
|
15
|
+
GraphStoreConnectionError,
|
|
16
|
+
GraphStoreError,
|
|
17
|
+
ErrorHandler,
|
|
18
|
+
ErrorContext,
|
|
19
|
+
ErrorSeverity,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DegradationMode(str, Enum):
|
|
26
|
+
"""Degradation mode"""
|
|
27
|
+
|
|
28
|
+
NORMAL = "normal" # Using primary backend
|
|
29
|
+
DEGRADED = "degraded" # Using fallback
|
|
30
|
+
FAILED = "failed" # Both backends failed
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class DegradationStatus:
|
|
35
|
+
"""Status of graceful degradation"""
|
|
36
|
+
|
|
37
|
+
mode: DegradationMode
|
|
38
|
+
primary_available: bool
|
|
39
|
+
fallback_available: bool
|
|
40
|
+
last_failure: Optional[str] = None
|
|
41
|
+
failure_count: int = 0
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
44
|
+
"""Convert to dictionary"""
|
|
45
|
+
return {
|
|
46
|
+
"mode": self.mode.value,
|
|
47
|
+
"primary_available": self.primary_available,
|
|
48
|
+
"fallback_available": self.fallback_available,
|
|
49
|
+
"last_failure": self.last_failure,
|
|
50
|
+
"failure_count": self.failure_count,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GracefulDegradationStore:
|
|
55
|
+
"""
|
|
56
|
+
Graph store with graceful degradation support
|
|
57
|
+
|
|
58
|
+
Automatically falls back to in-memory storage when primary backend fails.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
```python
|
|
62
|
+
primary = PostgresGraphStore(...)
|
|
63
|
+
store = GracefulDegradationStore(primary)
|
|
64
|
+
await store.initialize()
|
|
65
|
+
|
|
66
|
+
# If PostgreSQL fails, automatically uses in-memory fallback
|
|
67
|
+
entity = await store.get_entity("entity_1")
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
primary_store: GraphStore,
|
|
74
|
+
enable_fallback: bool = True,
|
|
75
|
+
max_failures: int = 3,
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Initialize graceful degradation store
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
primary_store: Primary graph store backend
|
|
82
|
+
enable_fallback: Enable automatic fallback
|
|
83
|
+
max_failures: Max failures before switching to fallback
|
|
84
|
+
"""
|
|
85
|
+
self.primary_store = primary_store
|
|
86
|
+
self.enable_fallback = enable_fallback
|
|
87
|
+
self.max_failures = max_failures
|
|
88
|
+
|
|
89
|
+
self.fallback_store: Optional[InMemoryGraphStore] = None
|
|
90
|
+
self.status = DegradationStatus(
|
|
91
|
+
mode=DegradationMode.NORMAL,
|
|
92
|
+
primary_available=True,
|
|
93
|
+
fallback_available=False,
|
|
94
|
+
)
|
|
95
|
+
self.failure_count = 0
|
|
96
|
+
self.error_handler = ErrorHandler()
|
|
97
|
+
|
|
98
|
+
async def initialize(self) -> None:
|
|
99
|
+
"""Initialize both primary and fallback stores"""
|
|
100
|
+
# Initialize primary
|
|
101
|
+
try:
|
|
102
|
+
await self.primary_store.initialize()
|
|
103
|
+
self.status.primary_available = True
|
|
104
|
+
logger.info("Primary store initialized successfully")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
self.status.primary_available = False
|
|
107
|
+
self.status.last_failure = str(e)
|
|
108
|
+
logger.error(f"Primary store initialization failed: {e}")
|
|
109
|
+
|
|
110
|
+
if self.enable_fallback:
|
|
111
|
+
await self._initialize_fallback()
|
|
112
|
+
|
|
113
|
+
# Initialize fallback if enabled
|
|
114
|
+
if self.enable_fallback and not self.fallback_store:
|
|
115
|
+
await self._initialize_fallback()
|
|
116
|
+
|
|
117
|
+
async def _initialize_fallback(self) -> None:
|
|
118
|
+
"""Initialize fallback in-memory store"""
|
|
119
|
+
try:
|
|
120
|
+
self.fallback_store = InMemoryGraphStore()
|
|
121
|
+
await self.fallback_store.initialize()
|
|
122
|
+
self.status.fallback_available = True
|
|
123
|
+
self.status.mode = DegradationMode.DEGRADED
|
|
124
|
+
logger.warning("Using fallback in-memory store")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
self.status.fallback_available = False
|
|
127
|
+
self.status.mode = DegradationMode.FAILED
|
|
128
|
+
logger.critical(f"Fallback store initialization failed: {e}")
|
|
129
|
+
|
|
130
|
+
async def close(self) -> None:
|
|
131
|
+
"""Close both stores"""
|
|
132
|
+
if self.primary_store:
|
|
133
|
+
try:
|
|
134
|
+
await self.primary_store.close()
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.warning(f"Error closing primary store: {e}")
|
|
137
|
+
|
|
138
|
+
if self.fallback_store:
|
|
139
|
+
try:
|
|
140
|
+
await self.fallback_store.close()
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.warning(f"Error closing fallback store: {e}")
|
|
143
|
+
|
|
144
|
+
def _get_active_store(self) -> GraphStore:
|
|
145
|
+
"""
|
|
146
|
+
Get the currently active store (primary or fallback)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Active graph store
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
GraphStoreError: If no store is available
|
|
153
|
+
"""
|
|
154
|
+
if self.status.primary_available:
|
|
155
|
+
return self.primary_store
|
|
156
|
+
elif self.status.fallback_available and self.fallback_store:
|
|
157
|
+
return self.fallback_store
|
|
158
|
+
else:
|
|
159
|
+
raise GraphStoreError("No graph store available (primary and fallback both failed)")
|
|
160
|
+
|
|
161
|
+
async def _execute_with_fallback(self, operation: str, func, *args, **kwargs):
|
|
162
|
+
"""
|
|
163
|
+
Execute operation with automatic fallback
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
operation: Operation name for error context
|
|
167
|
+
func: Function to execute
|
|
168
|
+
*args: Function arguments
|
|
169
|
+
**kwargs: Function keyword arguments
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Function result
|
|
173
|
+
"""
|
|
174
|
+
# Try primary first
|
|
175
|
+
if self.status.primary_available:
|
|
176
|
+
try:
|
|
177
|
+
result = await func(self.primary_store, *args, **kwargs)
|
|
178
|
+
|
|
179
|
+
# Reset failure count on success
|
|
180
|
+
if self.failure_count > 0:
|
|
181
|
+
logger.info("Primary store recovered, switching back")
|
|
182
|
+
self.failure_count = 0
|
|
183
|
+
self.status.primary_available = True
|
|
184
|
+
self.status.mode = DegradationMode.NORMAL
|
|
185
|
+
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
except (GraphStoreConnectionError, Exception) as e:
|
|
189
|
+
self.failure_count += 1
|
|
190
|
+
self.status.last_failure = str(e)
|
|
191
|
+
|
|
192
|
+
# Log error
|
|
193
|
+
self.error_handler.handle_error(
|
|
194
|
+
e,
|
|
195
|
+
ErrorContext(operation=operation, severity=ErrorSeverity.HIGH),
|
|
196
|
+
reraise=False,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Switch to fallback if threshold reached
|
|
200
|
+
if self.failure_count >= self.max_failures:
|
|
201
|
+
logger.warning(f"Primary store failed {self.failure_count} times, " f"switching to fallback")
|
|
202
|
+
self.status.primary_available = False
|
|
203
|
+
self.status.mode = DegradationMode.DEGRADED
|
|
204
|
+
|
|
205
|
+
# Try fallback if available
|
|
206
|
+
if self.status.fallback_available and self.fallback_store:
|
|
207
|
+
try:
|
|
208
|
+
return await func(self.fallback_store, *args, **kwargs)
|
|
209
|
+
except Exception as fallback_error:
|
|
210
|
+
logger.error(f"Fallback store also failed: {fallback_error}")
|
|
211
|
+
self.status.fallback_available = False
|
|
212
|
+
self.status.mode = DegradationMode.FAILED
|
|
213
|
+
raise GraphStoreError(f"Both primary and fallback stores failed. " f"Primary: {e}, Fallback: {fallback_error}")
|
|
214
|
+
else:
|
|
215
|
+
# No fallback available, raise original error
|
|
216
|
+
raise
|
|
217
|
+
|
|
218
|
+
# Use fallback if primary unavailable
|
|
219
|
+
elif self.status.fallback_available and self.fallback_store:
|
|
220
|
+
try:
|
|
221
|
+
return await func(self.fallback_store, *args, **kwargs)
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.error(f"Fallback store failed: {e}")
|
|
224
|
+
self.status.fallback_available = False
|
|
225
|
+
self.status.mode = DegradationMode.FAILED
|
|
226
|
+
raise GraphStoreError(f"Fallback store failed: {e}")
|
|
227
|
+
|
|
228
|
+
else:
|
|
229
|
+
raise GraphStoreError("No graph store available")
|
|
230
|
+
|
|
231
|
+
# Delegate all GraphStore methods with fallback
|
|
232
|
+
async def add_entity(self, entity):
|
|
233
|
+
"""Add entity with fallback"""
|
|
234
|
+
return await self._execute_with_fallback("add_entity", lambda store, e: store.add_entity(e), entity)
|
|
235
|
+
|
|
236
|
+
async def get_entity(self, entity_id: str):
|
|
237
|
+
"""Get entity with fallback"""
|
|
238
|
+
return await self._execute_with_fallback("get_entity", lambda store, eid: store.get_entity(eid), entity_id)
|
|
239
|
+
|
|
240
|
+
async def add_relation(self, relation):
|
|
241
|
+
"""Add relation with fallback"""
|
|
242
|
+
return await self._execute_with_fallback("add_relation", lambda store, r: store.add_relation(r), relation)
|
|
243
|
+
|
|
244
|
+
async def get_relation(self, relation_id: str):
|
|
245
|
+
"""Get relation with fallback"""
|
|
246
|
+
return await self._execute_with_fallback(
|
|
247
|
+
"get_relation",
|
|
248
|
+
lambda store, rid: store.get_relation(rid),
|
|
249
|
+
relation_id,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
async def get_neighbors(self, entity_id: str, **kwargs):
|
|
253
|
+
"""Get neighbors with fallback"""
|
|
254
|
+
return await self._execute_with_fallback(
|
|
255
|
+
"get_neighbors",
|
|
256
|
+
lambda store, eid, **kw: store.get_neighbors(eid, **kw),
|
|
257
|
+
entity_id,
|
|
258
|
+
**kwargs,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
async def get_stats(self):
|
|
262
|
+
"""Get stats with fallback"""
|
|
263
|
+
return await self._execute_with_fallback("get_stats", lambda store: store.get_stats())
|
|
264
|
+
|
|
265
|
+
def get_degradation_status(self) -> DegradationStatus:
|
|
266
|
+
"""Get current degradation status"""
|
|
267
|
+
return self.status
|
|
268
|
+
|
|
269
|
+
async def try_recover_primary(self) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
Attempt to recover primary store
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if recovery successful, False otherwise
|
|
275
|
+
"""
|
|
276
|
+
if self.status.primary_available:
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
# Try to reinitialize primary
|
|
281
|
+
await self.primary_store.initialize()
|
|
282
|
+
|
|
283
|
+
# Test with a simple operation (available via StatsMixinProtocol)
|
|
284
|
+
await self.primary_store.get_stats() # type: ignore[attr-defined]
|
|
285
|
+
|
|
286
|
+
self.status.primary_available = True
|
|
287
|
+
self.status.mode = DegradationMode.NORMAL
|
|
288
|
+
self.failure_count = 0
|
|
289
|
+
logger.info("Primary store recovered successfully")
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.warning(f"Primary store recovery failed: {e}")
|
|
294
|
+
return False
|