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,691 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema Manager
|
|
3
|
+
|
|
4
|
+
Service for managing knowledge graph schemas with CRUD operations.
|
|
5
|
+
Supports multi-tenancy with tenant-scoped schemas and global fallback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, List, Dict, Any, Type
|
|
9
|
+
from enum import Enum
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from aiecs.domain.knowledge_graph.schema.graph_schema import GraphSchema
|
|
13
|
+
from aiecs.domain.knowledge_graph.schema.entity_type import EntityType
|
|
14
|
+
from aiecs.domain.knowledge_graph.schema.relation_type import RelationType
|
|
15
|
+
from aiecs.domain.knowledge_graph.schema.property_schema import PropertySchema
|
|
16
|
+
from aiecs.domain.knowledge_graph.schema.type_enums import TypeEnumGenerator
|
|
17
|
+
from aiecs.infrastructure.graph_storage.schema_cache import LRUCache
|
|
18
|
+
from aiecs.infrastructure.graph_storage.tenant import TenantContext
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SchemaManager:
|
|
22
|
+
"""
|
|
23
|
+
Schema Manager Service
|
|
24
|
+
|
|
25
|
+
Manages knowledge graph schemas with support for:
|
|
26
|
+
- Creating, reading, updating, deleting entity and relation types
|
|
27
|
+
- Schema persistence (save/load from JSON)
|
|
28
|
+
- Schema validation
|
|
29
|
+
- Transaction-like operations (commit/rollback)
|
|
30
|
+
- LRU caching with TTL for performance optimization
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```python
|
|
34
|
+
manager = SchemaManager(cache_size=1000, ttl_seconds=3600)
|
|
35
|
+
|
|
36
|
+
# Add entity type
|
|
37
|
+
person_type = EntityType(name="Person", ...)
|
|
38
|
+
manager.create_entity_type(person_type)
|
|
39
|
+
|
|
40
|
+
# Get entity type (cached)
|
|
41
|
+
person = manager.get_entity_type("Person")
|
|
42
|
+
|
|
43
|
+
# Check cache stats
|
|
44
|
+
stats = manager.get_cache_stats()
|
|
45
|
+
print(f"Cache hit rate: {stats['hit_rate']:.2%}")
|
|
46
|
+
|
|
47
|
+
# Save schema
|
|
48
|
+
manager.save("./schema.json")
|
|
49
|
+
```
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
schema: Optional[GraphSchema] = None,
|
|
55
|
+
cache_size: int = 1000,
|
|
56
|
+
ttl_seconds: Optional[int] = 3600,
|
|
57
|
+
enable_cache: bool = True,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize schema manager with multi-tenancy support
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
schema: Initial global schema (default: empty schema)
|
|
64
|
+
cache_size: Maximum cache size (default: 1000)
|
|
65
|
+
ttl_seconds: Cache TTL in seconds (default: 3600, None = no expiration)
|
|
66
|
+
enable_cache: Whether to enable caching (default: True)
|
|
67
|
+
"""
|
|
68
|
+
# Global schema (tenant_id=None)
|
|
69
|
+
self.schema = schema if schema is not None else GraphSchema()
|
|
70
|
+
self._transaction_schema: Optional[GraphSchema] = None
|
|
71
|
+
|
|
72
|
+
# Tenant-scoped schemas: tenant_id -> GraphSchema
|
|
73
|
+
self._tenant_schemas: Dict[str, GraphSchema] = {}
|
|
74
|
+
|
|
75
|
+
# Initialize caches
|
|
76
|
+
self._enable_cache = enable_cache
|
|
77
|
+
# Declare cache variables as Optional before if/else to avoid type conflicts
|
|
78
|
+
self._entity_type_cache: Optional[LRUCache[EntityType]]
|
|
79
|
+
self._relation_type_cache: Optional[LRUCache[RelationType]]
|
|
80
|
+
self._property_cache: Optional[LRUCache[PropertySchema]]
|
|
81
|
+
if enable_cache:
|
|
82
|
+
self._entity_type_cache = LRUCache(max_size=cache_size, ttl_seconds=ttl_seconds)
|
|
83
|
+
self._relation_type_cache = LRUCache(max_size=cache_size, ttl_seconds=ttl_seconds)
|
|
84
|
+
self._property_cache = LRUCache(max_size=cache_size, ttl_seconds=ttl_seconds)
|
|
85
|
+
else:
|
|
86
|
+
self._entity_type_cache = None
|
|
87
|
+
self._relation_type_cache = None
|
|
88
|
+
self._property_cache = None
|
|
89
|
+
|
|
90
|
+
def _get_schema(self, tenant_id: Optional[str] = None) -> GraphSchema:
|
|
91
|
+
"""
|
|
92
|
+
Get schema for a specific tenant (with fallback to global)
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
tenant_id: Tenant ID (None = global schema)
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
GraphSchema for the tenant (or global if tenant schema doesn't exist)
|
|
99
|
+
"""
|
|
100
|
+
if tenant_id is None:
|
|
101
|
+
return self.schema
|
|
102
|
+
|
|
103
|
+
# Return tenant-specific schema if it exists, otherwise use global
|
|
104
|
+
return self._tenant_schemas.get(tenant_id, self.schema)
|
|
105
|
+
|
|
106
|
+
def _ensure_tenant_schema(self, tenant_id: str) -> GraphSchema:
|
|
107
|
+
"""
|
|
108
|
+
Ensure a tenant-specific schema exists (creates if needed)
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
tenant_id: Tenant ID
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Tenant-specific GraphSchema
|
|
115
|
+
|
|
116
|
+
Note:
|
|
117
|
+
Creates an empty schema for the tenant. Global types are accessible
|
|
118
|
+
via fallback mechanism in get_entity_type/get_relation_type methods.
|
|
119
|
+
"""
|
|
120
|
+
if tenant_id not in self._tenant_schemas:
|
|
121
|
+
# Create new empty schema for tenant
|
|
122
|
+
# Fallback to global schema happens in get methods
|
|
123
|
+
self._tenant_schemas[tenant_id] = GraphSchema()
|
|
124
|
+
|
|
125
|
+
return self._tenant_schemas[tenant_id]
|
|
126
|
+
|
|
127
|
+
def _make_cache_key(self, type_name: str, tenant_id: Optional[str] = None) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Create cache key with tenant scope
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
type_name: Type name
|
|
133
|
+
tenant_id: Tenant ID (None = global)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Cache key string
|
|
137
|
+
"""
|
|
138
|
+
if tenant_id is None:
|
|
139
|
+
return type_name
|
|
140
|
+
return f"{tenant_id}:{type_name}"
|
|
141
|
+
|
|
142
|
+
# Entity Type Operations
|
|
143
|
+
|
|
144
|
+
def create_entity_type(self, entity_type: EntityType, tenant_id: Optional[str] = None) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Create a new entity type (tenant-scoped or global)
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
entity_type: Entity type to create
|
|
150
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ValueError: If entity type already exists
|
|
154
|
+
|
|
155
|
+
Note:
|
|
156
|
+
Tenant schemas are independent from global schema. The same type name
|
|
157
|
+
can exist in both global and tenant schemas with different definitions.
|
|
158
|
+
"""
|
|
159
|
+
schema = self._ensure_tenant_schema(tenant_id) if tenant_id else self.schema
|
|
160
|
+
|
|
161
|
+
# Safety check: ensure tenant schema is not the global schema
|
|
162
|
+
if tenant_id is not None and schema is self.schema:
|
|
163
|
+
raise RuntimeError(f"Internal error: tenant schema for '{tenant_id}' is referencing global schema")
|
|
164
|
+
|
|
165
|
+
schema.add_entity_type(entity_type)
|
|
166
|
+
|
|
167
|
+
# Cache the new entity type with tenant-scoped key
|
|
168
|
+
if self._enable_cache and self._entity_type_cache:
|
|
169
|
+
cache_key = self._make_cache_key(entity_type.name, tenant_id)
|
|
170
|
+
self._entity_type_cache.set(cache_key, entity_type)
|
|
171
|
+
|
|
172
|
+
def register_entity_type(self, entity_type: EntityType, tenant_id: Optional[str] = None) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Register a new entity type (alias for create_entity_type)
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
entity_type: Entity type to register
|
|
178
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: If entity type already exists
|
|
182
|
+
"""
|
|
183
|
+
self.create_entity_type(entity_type, tenant_id=tenant_id)
|
|
184
|
+
|
|
185
|
+
def update_entity_type(self, entity_type: EntityType, tenant_id: Optional[str] = None) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Update an existing entity type (tenant-scoped or global)
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
entity_type: Updated entity type
|
|
191
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If entity type doesn't exist
|
|
195
|
+
"""
|
|
196
|
+
schema = self._get_schema(tenant_id)
|
|
197
|
+
schema.update_entity_type(entity_type)
|
|
198
|
+
|
|
199
|
+
# Invalidate cache for this entity type
|
|
200
|
+
if self._enable_cache and self._entity_type_cache:
|
|
201
|
+
cache_key = self._make_cache_key(entity_type.name, tenant_id)
|
|
202
|
+
self._entity_type_cache.delete(cache_key)
|
|
203
|
+
|
|
204
|
+
def delete_entity_type(self, type_name: str, tenant_id: Optional[str] = None) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Delete an entity type (tenant-scoped or global)
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
type_name: Name of entity type to delete
|
|
210
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
ValueError: If entity type doesn't exist or is in use
|
|
214
|
+
"""
|
|
215
|
+
schema = self._get_schema(tenant_id)
|
|
216
|
+
schema.delete_entity_type(type_name)
|
|
217
|
+
|
|
218
|
+
# Invalidate cache for this entity type
|
|
219
|
+
if self._enable_cache and self._entity_type_cache:
|
|
220
|
+
cache_key = self._make_cache_key(type_name, tenant_id)
|
|
221
|
+
self._entity_type_cache.delete(cache_key)
|
|
222
|
+
|
|
223
|
+
def get_entity_type(self, type_name: str, tenant_id: Optional[str] = None) -> Optional[EntityType]:
|
|
224
|
+
"""
|
|
225
|
+
Get an entity type by name (with caching and tenant-scoped fallback)
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
type_name: Name of entity type
|
|
229
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Entity type or None if not found
|
|
233
|
+
|
|
234
|
+
Note:
|
|
235
|
+
Falls back to global schema if tenant schema doesn't have the type
|
|
236
|
+
"""
|
|
237
|
+
cache_key = self._make_cache_key(type_name, tenant_id)
|
|
238
|
+
|
|
239
|
+
# Try cache first
|
|
240
|
+
if self._enable_cache and self._entity_type_cache:
|
|
241
|
+
cached = self._entity_type_cache.get(cache_key)
|
|
242
|
+
if cached is not None:
|
|
243
|
+
return cached
|
|
244
|
+
|
|
245
|
+
# Load from tenant schema first
|
|
246
|
+
if tenant_id is not None:
|
|
247
|
+
tenant_schema = self._tenant_schemas.get(tenant_id)
|
|
248
|
+
if tenant_schema is not None:
|
|
249
|
+
entity_type = tenant_schema.get_entity_type(type_name)
|
|
250
|
+
if entity_type is not None:
|
|
251
|
+
# Cache the tenant-specific result
|
|
252
|
+
if self._enable_cache and self._entity_type_cache:
|
|
253
|
+
self._entity_type_cache.set(cache_key, entity_type)
|
|
254
|
+
return entity_type
|
|
255
|
+
|
|
256
|
+
# Fall back to global schema
|
|
257
|
+
entity_type = self.schema.get_entity_type(type_name)
|
|
258
|
+
|
|
259
|
+
# Cache the result if found
|
|
260
|
+
if entity_type is not None and self._enable_cache and self._entity_type_cache:
|
|
261
|
+
self._entity_type_cache.set(cache_key, entity_type)
|
|
262
|
+
|
|
263
|
+
return entity_type
|
|
264
|
+
|
|
265
|
+
def list_entity_types(self, tenant_id: Optional[str] = None) -> List[str]:
|
|
266
|
+
"""
|
|
267
|
+
List all entity type names (tenant-scoped or global)
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
List of entity type names (includes tenant-specific + global if tenant_id provided)
|
|
274
|
+
|
|
275
|
+
Note:
|
|
276
|
+
When tenant_id is provided, returns union of tenant-specific and global types
|
|
277
|
+
"""
|
|
278
|
+
schema = self._get_schema(tenant_id)
|
|
279
|
+
tenant_types = set(schema.get_entity_type_names())
|
|
280
|
+
|
|
281
|
+
# If querying tenant schema, also include global types (fallback)
|
|
282
|
+
if tenant_id is not None and tenant_id in self._tenant_schemas:
|
|
283
|
+
global_types = set(self.schema.get_entity_type_names())
|
|
284
|
+
tenant_types.update(global_types)
|
|
285
|
+
|
|
286
|
+
return sorted(list(tenant_types))
|
|
287
|
+
|
|
288
|
+
# Relation Type Operations
|
|
289
|
+
|
|
290
|
+
def create_relation_type(self, relation_type: RelationType, tenant_id: Optional[str] = None) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Create a new relation type (tenant-scoped or global)
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
relation_type: Relation type to create
|
|
296
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
ValueError: If relation type already exists
|
|
300
|
+
"""
|
|
301
|
+
schema = self._ensure_tenant_schema(tenant_id) if tenant_id else self.schema
|
|
302
|
+
schema.add_relation_type(relation_type)
|
|
303
|
+
|
|
304
|
+
# Cache the new relation type with tenant-scoped key
|
|
305
|
+
if self._enable_cache and self._relation_type_cache:
|
|
306
|
+
cache_key = self._make_cache_key(relation_type.name, tenant_id)
|
|
307
|
+
self._relation_type_cache.set(cache_key, relation_type)
|
|
308
|
+
|
|
309
|
+
def update_relation_type(self, relation_type: RelationType, tenant_id: Optional[str] = None) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Update an existing relation type (tenant-scoped or global)
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
relation_type: Updated relation type
|
|
315
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
ValueError: If relation type doesn't exist
|
|
319
|
+
"""
|
|
320
|
+
schema = self._get_schema(tenant_id)
|
|
321
|
+
schema.update_relation_type(relation_type)
|
|
322
|
+
|
|
323
|
+
# Invalidate cache for this relation type
|
|
324
|
+
if self._enable_cache and self._relation_type_cache:
|
|
325
|
+
cache_key = self._make_cache_key(relation_type.name, tenant_id)
|
|
326
|
+
self._relation_type_cache.delete(cache_key)
|
|
327
|
+
|
|
328
|
+
def delete_relation_type(self, type_name: str, tenant_id: Optional[str] = None) -> None:
|
|
329
|
+
"""
|
|
330
|
+
Delete a relation type (tenant-scoped or global)
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
type_name: Name of relation type to delete
|
|
334
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
ValueError: If relation type doesn't exist
|
|
338
|
+
"""
|
|
339
|
+
schema = self._get_schema(tenant_id)
|
|
340
|
+
schema.delete_relation_type(type_name)
|
|
341
|
+
|
|
342
|
+
# Invalidate cache for this relation type
|
|
343
|
+
if self._enable_cache and self._relation_type_cache:
|
|
344
|
+
cache_key = self._make_cache_key(type_name, tenant_id)
|
|
345
|
+
self._relation_type_cache.delete(cache_key)
|
|
346
|
+
|
|
347
|
+
def get_relation_type(self, type_name: str, tenant_id: Optional[str] = None) -> Optional[RelationType]:
|
|
348
|
+
"""
|
|
349
|
+
Get a relation type by name (with caching and tenant-scoped fallback)
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
type_name: Name of relation type
|
|
353
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Relation type or None if not found
|
|
357
|
+
|
|
358
|
+
Note:
|
|
359
|
+
Falls back to global schema if tenant schema doesn't have the type
|
|
360
|
+
"""
|
|
361
|
+
cache_key = self._make_cache_key(type_name, tenant_id)
|
|
362
|
+
|
|
363
|
+
# Try cache first
|
|
364
|
+
if self._enable_cache and self._relation_type_cache:
|
|
365
|
+
cached = self._relation_type_cache.get(cache_key)
|
|
366
|
+
if cached is not None:
|
|
367
|
+
return cached
|
|
368
|
+
|
|
369
|
+
# Load from tenant schema first
|
|
370
|
+
if tenant_id is not None:
|
|
371
|
+
tenant_schema = self._tenant_schemas.get(tenant_id)
|
|
372
|
+
if tenant_schema is not None:
|
|
373
|
+
relation_type = tenant_schema.get_relation_type(type_name)
|
|
374
|
+
if relation_type is not None:
|
|
375
|
+
# Cache the tenant-specific result
|
|
376
|
+
if self._enable_cache and self._relation_type_cache:
|
|
377
|
+
self._relation_type_cache.set(cache_key, relation_type)
|
|
378
|
+
return relation_type
|
|
379
|
+
|
|
380
|
+
# Fall back to global schema
|
|
381
|
+
relation_type = self.schema.get_relation_type(type_name)
|
|
382
|
+
|
|
383
|
+
# Cache the result if found
|
|
384
|
+
if relation_type is not None and self._enable_cache and self._relation_type_cache:
|
|
385
|
+
self._relation_type_cache.set(cache_key, relation_type)
|
|
386
|
+
|
|
387
|
+
return relation_type
|
|
388
|
+
|
|
389
|
+
def list_relation_types(self, tenant_id: Optional[str] = None) -> List[str]:
|
|
390
|
+
"""
|
|
391
|
+
List all relation type names (tenant-scoped or global)
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
tenant_id: Tenant ID for tenant-scoped schema (None = global)
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
List of relation type names (includes tenant-specific + global if tenant_id provided)
|
|
398
|
+
|
|
399
|
+
Note:
|
|
400
|
+
When tenant_id is provided, returns union of tenant-specific and global types
|
|
401
|
+
"""
|
|
402
|
+
schema = self._get_schema(tenant_id)
|
|
403
|
+
tenant_types = set(schema.get_relation_type_names())
|
|
404
|
+
|
|
405
|
+
# If querying tenant schema, also include global types (fallback)
|
|
406
|
+
if tenant_id is not None and tenant_id in self._tenant_schemas:
|
|
407
|
+
global_types = set(self.schema.get_relation_type_names())
|
|
408
|
+
tenant_types.update(global_types)
|
|
409
|
+
|
|
410
|
+
return sorted(list(tenant_types))
|
|
411
|
+
|
|
412
|
+
# Schema Validation
|
|
413
|
+
|
|
414
|
+
def validate_entity(self, entity_type_name: str, properties: dict) -> bool:
|
|
415
|
+
"""
|
|
416
|
+
Validate entity properties against schema
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
entity_type_name: Name of entity type
|
|
420
|
+
properties: Dictionary of properties to validate
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
True if validation passes
|
|
424
|
+
|
|
425
|
+
Raises:
|
|
426
|
+
ValueError: If validation fails
|
|
427
|
+
"""
|
|
428
|
+
entity_type = self.get_entity_type(entity_type_name)
|
|
429
|
+
if entity_type is None:
|
|
430
|
+
raise ValueError(f"Entity type '{entity_type_name}' not found in schema")
|
|
431
|
+
|
|
432
|
+
return entity_type.validate_properties(properties)
|
|
433
|
+
|
|
434
|
+
def validate_relation(
|
|
435
|
+
self,
|
|
436
|
+
relation_type_name: str,
|
|
437
|
+
source_entity_type: str,
|
|
438
|
+
target_entity_type: str,
|
|
439
|
+
properties: dict,
|
|
440
|
+
) -> bool:
|
|
441
|
+
"""
|
|
442
|
+
Validate relation against schema
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
relation_type_name: Name of relation type
|
|
446
|
+
source_entity_type: Source entity type name
|
|
447
|
+
target_entity_type: Target entity type name
|
|
448
|
+
properties: Dictionary of properties to validate
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
True if validation passes
|
|
452
|
+
|
|
453
|
+
Raises:
|
|
454
|
+
ValueError: If validation fails
|
|
455
|
+
"""
|
|
456
|
+
relation_type = self.get_relation_type(relation_type_name)
|
|
457
|
+
if relation_type is None:
|
|
458
|
+
raise ValueError(f"Relation type '{relation_type_name}' not found in schema")
|
|
459
|
+
|
|
460
|
+
# Validate entity types
|
|
461
|
+
relation_type.validate_entity_types(source_entity_type, target_entity_type)
|
|
462
|
+
|
|
463
|
+
# Validate properties
|
|
464
|
+
return relation_type.validate_properties(properties)
|
|
465
|
+
|
|
466
|
+
# Schema Persistence
|
|
467
|
+
|
|
468
|
+
def save(self, file_path: str) -> None:
|
|
469
|
+
"""
|
|
470
|
+
Save schema to JSON file
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
file_path: Path to save schema
|
|
474
|
+
"""
|
|
475
|
+
schema_dict = self.schema.model_dump(mode="json")
|
|
476
|
+
|
|
477
|
+
path = Path(file_path)
|
|
478
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
479
|
+
|
|
480
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
481
|
+
json.dump(schema_dict, f, indent=2, ensure_ascii=False)
|
|
482
|
+
|
|
483
|
+
@classmethod
|
|
484
|
+
def load(cls, file_path: str) -> "SchemaManager":
|
|
485
|
+
"""
|
|
486
|
+
Load schema from JSON file
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
file_path: Path to load schema from
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
New SchemaManager instance with loaded schema
|
|
493
|
+
"""
|
|
494
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
495
|
+
schema_dict = json.load(f)
|
|
496
|
+
|
|
497
|
+
schema = GraphSchema(**schema_dict)
|
|
498
|
+
return cls(schema=schema)
|
|
499
|
+
|
|
500
|
+
# Transaction Support (Simple)
|
|
501
|
+
|
|
502
|
+
def begin_transaction(self) -> None:
|
|
503
|
+
"""Begin a schema transaction"""
|
|
504
|
+
# Create a deep copy of the current schema
|
|
505
|
+
schema_json = self.schema.model_dump_json()
|
|
506
|
+
self._transaction_schema = GraphSchema(**json.loads(schema_json))
|
|
507
|
+
|
|
508
|
+
def commit(self) -> None:
|
|
509
|
+
"""Commit the current transaction"""
|
|
510
|
+
self._transaction_schema = None
|
|
511
|
+
|
|
512
|
+
def rollback(self) -> None:
|
|
513
|
+
"""
|
|
514
|
+
Rollback to the state at transaction start
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
RuntimeError: If no transaction is active
|
|
518
|
+
"""
|
|
519
|
+
if self._transaction_schema is None:
|
|
520
|
+
raise RuntimeError("No active transaction to rollback")
|
|
521
|
+
|
|
522
|
+
self.schema = self._transaction_schema
|
|
523
|
+
self._transaction_schema = None
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def is_in_transaction(self) -> bool:
|
|
527
|
+
"""Check if a transaction is active"""
|
|
528
|
+
return self._transaction_schema is not None
|
|
529
|
+
|
|
530
|
+
# Cache Management
|
|
531
|
+
|
|
532
|
+
def invalidate_cache(self, type_name: Optional[str] = None, tenant_id: Optional[str] = None) -> None:
|
|
533
|
+
"""
|
|
534
|
+
Invalidate cache entries (tenant-scoped or global)
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
type_name: Specific type to invalidate (None = invalidate all)
|
|
538
|
+
tenant_id: Tenant ID for tenant-scoped invalidation (None = global, "*" = all tenants)
|
|
539
|
+
"""
|
|
540
|
+
if not self._enable_cache:
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
if type_name is None:
|
|
544
|
+
if tenant_id == "*":
|
|
545
|
+
# Clear all caches (all tenants)
|
|
546
|
+
if self._entity_type_cache:
|
|
547
|
+
self._entity_type_cache.clear()
|
|
548
|
+
if self._relation_type_cache:
|
|
549
|
+
self._relation_type_cache.clear()
|
|
550
|
+
if self._property_cache:
|
|
551
|
+
self._property_cache.clear()
|
|
552
|
+
elif tenant_id is not None:
|
|
553
|
+
# Clear only entries for specific tenant
|
|
554
|
+
self._invalidate_tenant_cache(tenant_id)
|
|
555
|
+
else:
|
|
556
|
+
# Clear global cache entries only
|
|
557
|
+
if self._entity_type_cache:
|
|
558
|
+
# Clear only non-tenant keys (no ":" in key)
|
|
559
|
+
for key in list(self._entity_type_cache._cache.keys()):
|
|
560
|
+
if ":" not in key:
|
|
561
|
+
self._entity_type_cache.delete(key)
|
|
562
|
+
if self._relation_type_cache:
|
|
563
|
+
for key in list(self._relation_type_cache._cache.keys()):
|
|
564
|
+
if ":" not in key:
|
|
565
|
+
self._relation_type_cache.delete(key)
|
|
566
|
+
else:
|
|
567
|
+
# Invalidate specific type with tenant scope
|
|
568
|
+
cache_key = self._make_cache_key(type_name, tenant_id)
|
|
569
|
+
if self._entity_type_cache:
|
|
570
|
+
self._entity_type_cache.delete(cache_key)
|
|
571
|
+
if self._relation_type_cache:
|
|
572
|
+
self._relation_type_cache.delete(cache_key)
|
|
573
|
+
|
|
574
|
+
def _invalidate_tenant_cache(self, tenant_id: str) -> None:
|
|
575
|
+
"""
|
|
576
|
+
Invalidate all cache entries for a specific tenant
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
tenant_id: Tenant ID to invalidate
|
|
580
|
+
"""
|
|
581
|
+
if not self._enable_cache:
|
|
582
|
+
return
|
|
583
|
+
|
|
584
|
+
tenant_prefix = f"{tenant_id}:"
|
|
585
|
+
|
|
586
|
+
# Remove all cache entries with this tenant prefix
|
|
587
|
+
if self._entity_type_cache:
|
|
588
|
+
for key in list(self._entity_type_cache._cache.keys()):
|
|
589
|
+
if key.startswith(tenant_prefix):
|
|
590
|
+
self._entity_type_cache.delete(key)
|
|
591
|
+
|
|
592
|
+
if self._relation_type_cache:
|
|
593
|
+
for key in list(self._relation_type_cache._cache.keys()):
|
|
594
|
+
if key.startswith(tenant_prefix):
|
|
595
|
+
self._relation_type_cache.delete(key)
|
|
596
|
+
|
|
597
|
+
if self._property_cache:
|
|
598
|
+
for key in list(self._property_cache._cache.keys()):
|
|
599
|
+
if key.startswith(tenant_prefix):
|
|
600
|
+
self._property_cache.delete(key)
|
|
601
|
+
|
|
602
|
+
def cleanup_expired_cache(self) -> Dict[str, int]:
|
|
603
|
+
"""
|
|
604
|
+
Remove expired cache entries
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
Dictionary with number of entries removed per cache
|
|
608
|
+
"""
|
|
609
|
+
if not self._enable_cache:
|
|
610
|
+
return {"entity_types": 0, "relation_types": 0, "properties": 0}
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
"entity_types": (self._entity_type_cache.cleanup_expired() if self._entity_type_cache else 0),
|
|
614
|
+
"relation_types": (self._relation_type_cache.cleanup_expired() if self._relation_type_cache else 0),
|
|
615
|
+
"properties": (self._property_cache.cleanup_expired() if self._property_cache else 0),
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
def get_cache_stats(self) -> Dict[str, Any]:
|
|
619
|
+
"""
|
|
620
|
+
Get cache statistics
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Dictionary with cache statistics for all caches
|
|
624
|
+
"""
|
|
625
|
+
if not self._enable_cache:
|
|
626
|
+
return {
|
|
627
|
+
"enabled": False,
|
|
628
|
+
"entity_types": {},
|
|
629
|
+
"relation_types": {},
|
|
630
|
+
"properties": {},
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return {
|
|
634
|
+
"enabled": True,
|
|
635
|
+
"entity_types": (self._entity_type_cache.get_stats() if self._entity_type_cache else {}),
|
|
636
|
+
"relation_types": (self._relation_type_cache.get_stats() if self._relation_type_cache else {}),
|
|
637
|
+
"properties": (self._property_cache.get_stats() if self._property_cache else {}),
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
def reset_cache_metrics(self) -> None:
|
|
641
|
+
"""Reset cache metrics (hits, misses, etc.)"""
|
|
642
|
+
if not self._enable_cache:
|
|
643
|
+
return
|
|
644
|
+
|
|
645
|
+
if self._entity_type_cache:
|
|
646
|
+
self._entity_type_cache.reset_metrics()
|
|
647
|
+
if self._relation_type_cache:
|
|
648
|
+
self._relation_type_cache.reset_metrics()
|
|
649
|
+
if self._property_cache:
|
|
650
|
+
self._property_cache.reset_metrics()
|
|
651
|
+
|
|
652
|
+
# Type Enum Generation (Task 3.4)
|
|
653
|
+
|
|
654
|
+
def generate_enums(self) -> Dict[str, Dict[str, Type[Enum]]]:
|
|
655
|
+
"""
|
|
656
|
+
Generate type enums from schema
|
|
657
|
+
|
|
658
|
+
Creates Python Enum classes for all entity types and relation types
|
|
659
|
+
defined in the schema. The generated enums are string-based for
|
|
660
|
+
backward compatibility with existing code.
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
Dictionary with "entity_types" and "relation_types" keys,
|
|
664
|
+
each containing a dictionary mapping type names to enum classes
|
|
665
|
+
|
|
666
|
+
Example:
|
|
667
|
+
>>> enums = schema_manager.generate_enums()
|
|
668
|
+
>>> PersonEnum = enums["entity_types"]["Person"]
|
|
669
|
+
>>> PersonEnum.PERSON # "Person"
|
|
670
|
+
>>>
|
|
671
|
+
>>> WorksForEnum = enums["relation_types"]["WORKS_FOR"]
|
|
672
|
+
>>> WorksForEnum.WORKS_FOR # "WORKS_FOR"
|
|
673
|
+
|
|
674
|
+
Note:
|
|
675
|
+
The generated enums are backward compatible with string literals:
|
|
676
|
+
>>> str(PersonEnum.PERSON) == "Person" # True
|
|
677
|
+
>>> PersonEnum.PERSON == "Person" # True
|
|
678
|
+
"""
|
|
679
|
+
generator = TypeEnumGenerator(self.schema)
|
|
680
|
+
return generator.generate_all_enums()
|
|
681
|
+
|
|
682
|
+
def __str__(self) -> str:
|
|
683
|
+
cache_info = ""
|
|
684
|
+
if self._enable_cache and self._entity_type_cache:
|
|
685
|
+
stats = self.get_cache_stats()
|
|
686
|
+
entity_hit_rate = stats["entity_types"].get("hit_rate", 0)
|
|
687
|
+
cache_info = f", cache_hit_rate={entity_hit_rate:.2%}"
|
|
688
|
+
return f"SchemaManager({self.schema}{cache_info})"
|
|
689
|
+
|
|
690
|
+
def __repr__(self) -> str:
|
|
691
|
+
return self.__str__()
|