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,866 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST Validator for Logic Query Parser
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive validation of AST nodes against the knowledge graph schema.
|
|
5
|
+
It validates entity types, properties, relation types, property types, and variable references.
|
|
6
|
+
|
|
7
|
+
Design Principles:
|
|
8
|
+
1. Schema-aware validation (entity types, properties, relations)
|
|
9
|
+
2. Error accumulation (collect all errors, don't stop at first)
|
|
10
|
+
3. Helpful error messages with suggestions
|
|
11
|
+
4. Type checking (property values match expected types)
|
|
12
|
+
|
|
13
|
+
Phase: 2.4 - Logic Query Parser
|
|
14
|
+
Task: 3.1 - Implement AST Validator
|
|
15
|
+
Version: 1.0
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any, List, Optional, Set
|
|
19
|
+
from .ast_nodes import (
|
|
20
|
+
ValidationError,
|
|
21
|
+
ASTNode,
|
|
22
|
+
QueryNode,
|
|
23
|
+
FindNode,
|
|
24
|
+
TraversalNode,
|
|
25
|
+
PropertyFilterNode,
|
|
26
|
+
BooleanFilterNode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ASTValidator:
|
|
31
|
+
"""
|
|
32
|
+
AST Validator with schema integration
|
|
33
|
+
|
|
34
|
+
This class provides comprehensive validation of AST nodes against the
|
|
35
|
+
knowledge graph schema. It validates:
|
|
36
|
+
- Entity types exist in schema
|
|
37
|
+
- Properties exist in entity schema
|
|
38
|
+
- Relation types exist in schema
|
|
39
|
+
- Property values match expected types
|
|
40
|
+
- Relation endpoints match entity types
|
|
41
|
+
- Variable references are defined before use
|
|
42
|
+
|
|
43
|
+
The validator accumulates all errors instead of stopping at the first error.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
```python
|
|
47
|
+
from aiecs.domain.knowledge_graph.schema import SchemaManager
|
|
48
|
+
|
|
49
|
+
schema = SchemaManager.load("schema.json")
|
|
50
|
+
validator = ASTValidator(schema)
|
|
51
|
+
|
|
52
|
+
errors = validator.validate(ast_node)
|
|
53
|
+
if errors:
|
|
54
|
+
for error in errors:
|
|
55
|
+
print(f"Line {error.line}: {error.message}")
|
|
56
|
+
```
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, schema: Any):
|
|
60
|
+
"""
|
|
61
|
+
Initialize AST validator
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
schema: SchemaManager instance for validation
|
|
65
|
+
"""
|
|
66
|
+
self.schema = schema
|
|
67
|
+
self.current_entity_type: Optional[str] = None
|
|
68
|
+
self.defined_variables: Set[str] = set()
|
|
69
|
+
|
|
70
|
+
# ========================================================================
|
|
71
|
+
# Main Validation Entry Point
|
|
72
|
+
# ========================================================================
|
|
73
|
+
|
|
74
|
+
def validate(self, node: ASTNode) -> List[ValidationError]:
|
|
75
|
+
"""
|
|
76
|
+
Validate an AST node and all its children
|
|
77
|
+
|
|
78
|
+
This is the main entry point for validation. It accumulates all
|
|
79
|
+
errors from the node and its children.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
node: AST node to validate
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of validation errors (empty if valid)
|
|
86
|
+
"""
|
|
87
|
+
errors = []
|
|
88
|
+
|
|
89
|
+
# Dispatch to specific validation method based on node type
|
|
90
|
+
if isinstance(node, QueryNode):
|
|
91
|
+
errors.extend(self.validate_query_node(node))
|
|
92
|
+
elif isinstance(node, FindNode):
|
|
93
|
+
errors.extend(self.validate_find_node(node))
|
|
94
|
+
elif isinstance(node, TraversalNode):
|
|
95
|
+
errors.extend(self.validate_traversal_node(node))
|
|
96
|
+
elif isinstance(node, PropertyFilterNode):
|
|
97
|
+
errors.extend(self.validate_property_filter_node(node))
|
|
98
|
+
elif isinstance(node, BooleanFilterNode):
|
|
99
|
+
errors.extend(self.validate_boolean_filter_node(node))
|
|
100
|
+
else:
|
|
101
|
+
# Fallback to node's own validate method
|
|
102
|
+
errors.extend(node.validate(self.schema))
|
|
103
|
+
|
|
104
|
+
return errors
|
|
105
|
+
|
|
106
|
+
# ========================================================================
|
|
107
|
+
# Node-Specific Validation Methods
|
|
108
|
+
# ========================================================================
|
|
109
|
+
|
|
110
|
+
def validate_query_node(self, node: QueryNode) -> List[ValidationError]:
|
|
111
|
+
"""
|
|
112
|
+
Validate QueryNode
|
|
113
|
+
|
|
114
|
+
Validates:
|
|
115
|
+
- FindNode
|
|
116
|
+
- All TraversalNodes
|
|
117
|
+
- Entity type consistency across traversals
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
node: QueryNode to validate
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of validation errors
|
|
124
|
+
"""
|
|
125
|
+
errors: List[ValidationError] = []
|
|
126
|
+
|
|
127
|
+
# Validate FindNode
|
|
128
|
+
errors.extend(self.validate_find_node(node.find))
|
|
129
|
+
|
|
130
|
+
# Track current entity type for traversal validation
|
|
131
|
+
self.current_entity_type = node.find.entity_type
|
|
132
|
+
|
|
133
|
+
# Validate all traversals
|
|
134
|
+
for traversal in node.traversals:
|
|
135
|
+
errors.extend(self.validate_traversal_node(traversal))
|
|
136
|
+
|
|
137
|
+
return errors
|
|
138
|
+
|
|
139
|
+
def validate_find_node(self, node: FindNode) -> List[ValidationError]:
|
|
140
|
+
"""
|
|
141
|
+
Validate FindNode
|
|
142
|
+
|
|
143
|
+
Validates:
|
|
144
|
+
- Entity type exists in schema
|
|
145
|
+
- All filters reference valid properties
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
node: FindNode to validate
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of validation errors
|
|
152
|
+
"""
|
|
153
|
+
errors: List[ValidationError] = []
|
|
154
|
+
|
|
155
|
+
# Validate entity type exists
|
|
156
|
+
errors.extend(self.validate_entity_type(node.entity_type, node.line, node.column))
|
|
157
|
+
|
|
158
|
+
# Set current entity type for property validation
|
|
159
|
+
self.current_entity_type = node.entity_type
|
|
160
|
+
|
|
161
|
+
# Validate all filters
|
|
162
|
+
for filter_node in node.filters:
|
|
163
|
+
errors.extend(self.validate(filter_node))
|
|
164
|
+
|
|
165
|
+
return errors
|
|
166
|
+
|
|
167
|
+
def validate_traversal_node(self, node: TraversalNode) -> List[ValidationError]:
|
|
168
|
+
"""
|
|
169
|
+
Validate TraversalNode
|
|
170
|
+
|
|
171
|
+
Validates:
|
|
172
|
+
- Relation type exists in schema
|
|
173
|
+
- Relation endpoints match current entity type
|
|
174
|
+
- Direction is valid
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
node: TraversalNode to validate
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of validation errors
|
|
181
|
+
"""
|
|
182
|
+
errors: List[ValidationError] = []
|
|
183
|
+
|
|
184
|
+
# Validate relation type exists
|
|
185
|
+
errors.extend(self.validate_relation_type(node.relation_type, node.line, node.column))
|
|
186
|
+
|
|
187
|
+
# Validate relation endpoints if we have a current entity type
|
|
188
|
+
if self.current_entity_type:
|
|
189
|
+
errors.extend(
|
|
190
|
+
self.validate_relation_endpoints(
|
|
191
|
+
node.relation_type,
|
|
192
|
+
self.current_entity_type,
|
|
193
|
+
node.direction or "outgoing",
|
|
194
|
+
node.line,
|
|
195
|
+
node.column,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Validate direction
|
|
200
|
+
if node.direction and node.direction not in ["incoming", "outgoing"]:
|
|
201
|
+
errors.append(
|
|
202
|
+
ValidationError(
|
|
203
|
+
line=node.line,
|
|
204
|
+
column=node.column,
|
|
205
|
+
message=f"Invalid direction '{node.direction}'. Must be 'incoming' or 'outgoing'",
|
|
206
|
+
suggestion="Use 'INCOMING' or 'OUTGOING'",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return errors
|
|
211
|
+
|
|
212
|
+
def validate_property_filter_node(self, node: PropertyFilterNode) -> List[ValidationError]:
|
|
213
|
+
"""
|
|
214
|
+
Validate PropertyFilterNode
|
|
215
|
+
|
|
216
|
+
Validates:
|
|
217
|
+
- Property exists in current entity type
|
|
218
|
+
- Property value type matches schema
|
|
219
|
+
- Operator is valid for property type
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
node: PropertyFilterNode to validate
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of validation errors
|
|
226
|
+
"""
|
|
227
|
+
errors: List[ValidationError] = []
|
|
228
|
+
|
|
229
|
+
# Validate operator
|
|
230
|
+
valid_operators = ["==", "!=", ">", "<", ">=", "<=", "IN", "CONTAINS"]
|
|
231
|
+
if node.operator not in valid_operators:
|
|
232
|
+
errors.append(
|
|
233
|
+
ValidationError(
|
|
234
|
+
line=node.line,
|
|
235
|
+
column=node.column,
|
|
236
|
+
message=f"Invalid operator '{node.operator}'",
|
|
237
|
+
suggestion=f"Valid operators: {', '.join(valid_operators)}",
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Validate property exists in current entity type
|
|
242
|
+
if self.current_entity_type:
|
|
243
|
+
errors.extend(
|
|
244
|
+
self.validate_property(
|
|
245
|
+
self.current_entity_type,
|
|
246
|
+
node.property_path,
|
|
247
|
+
node.line,
|
|
248
|
+
node.column,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Validate property value type
|
|
253
|
+
errors.extend(
|
|
254
|
+
self.validate_property_value_type(
|
|
255
|
+
self.current_entity_type,
|
|
256
|
+
node.property_path,
|
|
257
|
+
node.value,
|
|
258
|
+
node.operator,
|
|
259
|
+
node.line,
|
|
260
|
+
node.column,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Validate operator-specific constraints
|
|
265
|
+
if node.operator == "IN" and not isinstance(node.value, list):
|
|
266
|
+
errors.append(
|
|
267
|
+
ValidationError(
|
|
268
|
+
line=node.line,
|
|
269
|
+
column=node.column,
|
|
270
|
+
message=f"IN operator requires a list value, got {type(node.value).__name__}",
|
|
271
|
+
suggestion="Use a list like ['value1', 'value2']",
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if node.operator == "CONTAINS" and not isinstance(node.value, str):
|
|
276
|
+
errors.append(
|
|
277
|
+
ValidationError(
|
|
278
|
+
line=node.line,
|
|
279
|
+
column=node.column,
|
|
280
|
+
message=f"CONTAINS operator requires a string value, got {type(node.value).__name__}",
|
|
281
|
+
suggestion="Use a string value",
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return errors
|
|
286
|
+
|
|
287
|
+
def validate_boolean_filter_node(self, node: BooleanFilterNode) -> List[ValidationError]:
|
|
288
|
+
"""
|
|
289
|
+
Validate BooleanFilterNode
|
|
290
|
+
|
|
291
|
+
Validates:
|
|
292
|
+
- Operator is valid (AND, OR, NOT)
|
|
293
|
+
- Has at least one operand
|
|
294
|
+
- All operands are valid
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
node: BooleanFilterNode to validate
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of validation errors
|
|
301
|
+
"""
|
|
302
|
+
errors: List[ValidationError] = []
|
|
303
|
+
|
|
304
|
+
# Validate operator
|
|
305
|
+
valid_operators = ["AND", "OR", "NOT"]
|
|
306
|
+
if node.operator not in valid_operators:
|
|
307
|
+
errors.append(
|
|
308
|
+
ValidationError(
|
|
309
|
+
line=node.line,
|
|
310
|
+
column=node.column,
|
|
311
|
+
message=f"Invalid boolean operator '{node.operator}'",
|
|
312
|
+
suggestion=f"Valid operators: {', '.join(valid_operators)}",
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Validate operand count
|
|
317
|
+
if not node.operands:
|
|
318
|
+
errors.append(
|
|
319
|
+
ValidationError(
|
|
320
|
+
line=node.line,
|
|
321
|
+
column=node.column,
|
|
322
|
+
message=f"Boolean operator '{node.operator}' requires at least one operand",
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Validate all operands
|
|
327
|
+
for operand in node.operands:
|
|
328
|
+
errors.extend(self.validate(operand))
|
|
329
|
+
|
|
330
|
+
return errors
|
|
331
|
+
|
|
332
|
+
# ========================================================================
|
|
333
|
+
# Helper Validation Methods
|
|
334
|
+
# ========================================================================
|
|
335
|
+
|
|
336
|
+
def validate_entity_type(self, entity_type: str, line: int, column: int) -> List[ValidationError]:
|
|
337
|
+
"""
|
|
338
|
+
Validate that an entity type exists in the schema
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
entity_type: Entity type name to validate
|
|
342
|
+
line: Line number for error reporting
|
|
343
|
+
column: Column number for error reporting
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
List of validation errors
|
|
347
|
+
"""
|
|
348
|
+
errors: List[ValidationError] = []
|
|
349
|
+
|
|
350
|
+
# Check if schema has the method
|
|
351
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
352
|
+
return errors
|
|
353
|
+
|
|
354
|
+
# Check if entity type exists
|
|
355
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
356
|
+
if entity_schema is None:
|
|
357
|
+
# Get available types for suggestion
|
|
358
|
+
available_types = []
|
|
359
|
+
if hasattr(self.schema, "list_entity_types"):
|
|
360
|
+
available_types = self.schema.list_entity_types()
|
|
361
|
+
|
|
362
|
+
suggestion = None
|
|
363
|
+
if available_types:
|
|
364
|
+
suggestion = f"Available entity types: {', '.join(available_types[:5])}"
|
|
365
|
+
if len(available_types) > 5:
|
|
366
|
+
suggestion += f" (and {len(available_types) - 5} more)"
|
|
367
|
+
|
|
368
|
+
errors.append(
|
|
369
|
+
ValidationError(
|
|
370
|
+
line=line,
|
|
371
|
+
column=column,
|
|
372
|
+
message=f"Entity type '{entity_type}' not found in schema",
|
|
373
|
+
suggestion=suggestion,
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
return errors
|
|
378
|
+
|
|
379
|
+
def validate_relation_type(self, relation_type: str, line: int, column: int) -> List[ValidationError]:
|
|
380
|
+
"""
|
|
381
|
+
Validate that a relation type exists in the schema
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
relation_type: Relation type name to validate
|
|
385
|
+
line: Line number for error reporting
|
|
386
|
+
column: Column number for error reporting
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
List of validation errors
|
|
390
|
+
"""
|
|
391
|
+
errors: List[ValidationError] = []
|
|
392
|
+
|
|
393
|
+
# Check if schema has the method
|
|
394
|
+
if not hasattr(self.schema, "get_relation_type"):
|
|
395
|
+
return errors
|
|
396
|
+
|
|
397
|
+
# Check if relation type exists
|
|
398
|
+
relation_schema = self.schema.get_relation_type(relation_type)
|
|
399
|
+
if relation_schema is None:
|
|
400
|
+
# Get available types for suggestion
|
|
401
|
+
available_types = []
|
|
402
|
+
if hasattr(self.schema, "list_relation_types"):
|
|
403
|
+
available_types = self.schema.list_relation_types()
|
|
404
|
+
|
|
405
|
+
suggestion = None
|
|
406
|
+
if available_types:
|
|
407
|
+
suggestion = f"Available relation types: {', '.join(available_types[:5])}"
|
|
408
|
+
if len(available_types) > 5:
|
|
409
|
+
suggestion += f" (and {len(available_types) - 5} more)"
|
|
410
|
+
|
|
411
|
+
errors.append(
|
|
412
|
+
ValidationError(
|
|
413
|
+
line=line,
|
|
414
|
+
column=column,
|
|
415
|
+
message=f"Relation type '{relation_type}' not found in schema",
|
|
416
|
+
suggestion=suggestion,
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return errors
|
|
421
|
+
|
|
422
|
+
def validate_property(self, entity_type: str, property_path: str, line: int, column: int) -> List[ValidationError]:
|
|
423
|
+
"""
|
|
424
|
+
Validate that a property exists in an entity type
|
|
425
|
+
|
|
426
|
+
Supports nested property paths (e.g., "address.city") by recursively
|
|
427
|
+
validating each part of the path.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
entity_type: Entity type name
|
|
431
|
+
property_path: Property path (may be nested like "address.city")
|
|
432
|
+
line: Line number for error reporting
|
|
433
|
+
column: Column number for error reporting
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
List of validation errors
|
|
437
|
+
"""
|
|
438
|
+
errors: List[ValidationError] = []
|
|
439
|
+
|
|
440
|
+
# Get entity type schema
|
|
441
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
442
|
+
return errors
|
|
443
|
+
|
|
444
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
445
|
+
if entity_schema is None:
|
|
446
|
+
return errors # Entity type error already reported
|
|
447
|
+
|
|
448
|
+
# Validate nested property path recursively
|
|
449
|
+
errors.extend(
|
|
450
|
+
self._validate_nested_property_path(
|
|
451
|
+
entity_schema=entity_schema,
|
|
452
|
+
property_path=property_path,
|
|
453
|
+
entity_type=entity_type,
|
|
454
|
+
line=line,
|
|
455
|
+
column=column,
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
return errors
|
|
460
|
+
|
|
461
|
+
def _validate_nested_property_path(
|
|
462
|
+
self,
|
|
463
|
+
entity_schema: Any,
|
|
464
|
+
property_path: str,
|
|
465
|
+
entity_type: str,
|
|
466
|
+
line: int,
|
|
467
|
+
column: int,
|
|
468
|
+
current_path: str = "",
|
|
469
|
+
) -> List[ValidationError]:
|
|
470
|
+
"""
|
|
471
|
+
Recursively validate a nested property path
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
entity_schema: Current entity schema (may be nested)
|
|
475
|
+
property_path: Remaining property path to validate
|
|
476
|
+
entity_type: Root entity type name
|
|
477
|
+
line: Line number for error reporting
|
|
478
|
+
column: Column number for error reporting
|
|
479
|
+
current_path: Accumulated path so far (for error messages)
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
List of validation errors
|
|
483
|
+
"""
|
|
484
|
+
errors: List[ValidationError] = []
|
|
485
|
+
|
|
486
|
+
# Split property path into parts
|
|
487
|
+
property_parts = property_path.split(".")
|
|
488
|
+
current_property = property_parts[0]
|
|
489
|
+
remaining_path = ".".join(property_parts[1:]) if len(property_parts) > 1 else None
|
|
490
|
+
|
|
491
|
+
# Build full path for error messages
|
|
492
|
+
full_path = f"{current_path}.{current_property}" if current_path else current_property
|
|
493
|
+
|
|
494
|
+
# Check if entity schema has get_property method
|
|
495
|
+
if not hasattr(entity_schema, "get_property"):
|
|
496
|
+
return errors
|
|
497
|
+
|
|
498
|
+
# Get property schema
|
|
499
|
+
property_schema = entity_schema.get_property(current_property)
|
|
500
|
+
if property_schema is None:
|
|
501
|
+
# Property doesn't exist - get available properties for suggestion
|
|
502
|
+
available_props = []
|
|
503
|
+
if hasattr(entity_schema, "properties"):
|
|
504
|
+
available_props = list(entity_schema.properties.keys())
|
|
505
|
+
elif hasattr(entity_schema, "get_property_names"):
|
|
506
|
+
available_props = entity_schema.get_property_names()
|
|
507
|
+
|
|
508
|
+
suggestion = None
|
|
509
|
+
if available_props:
|
|
510
|
+
context = f"{entity_type}.{current_path}" if current_path else entity_type
|
|
511
|
+
suggestion = f"Available properties for {context}: {', '.join(available_props[:5])}"
|
|
512
|
+
if len(available_props) > 5:
|
|
513
|
+
suggestion += f" (and {len(available_props) - 5} more)"
|
|
514
|
+
|
|
515
|
+
errors.append(
|
|
516
|
+
ValidationError(
|
|
517
|
+
line=line,
|
|
518
|
+
column=column,
|
|
519
|
+
message=f"Property '{full_path}' not found in {entity_type if not current_path else current_path}",
|
|
520
|
+
suggestion=suggestion,
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
return errors # Can't continue validation if property doesn't exist
|
|
524
|
+
|
|
525
|
+
# Check if there's more nesting to validate
|
|
526
|
+
if remaining_path:
|
|
527
|
+
# Check if current property is DICT type (supports nesting)
|
|
528
|
+
if not hasattr(property_schema, "property_type"):
|
|
529
|
+
# Can't determine if nesting is supported
|
|
530
|
+
errors.append(
|
|
531
|
+
ValidationError(
|
|
532
|
+
line=line,
|
|
533
|
+
column=column,
|
|
534
|
+
message=f"Cannot validate nested path '{full_path}.{remaining_path}': "
|
|
535
|
+
f"property '{current_property}' type unknown",
|
|
536
|
+
suggestion="Ensure property schema defines property_type",
|
|
537
|
+
)
|
|
538
|
+
)
|
|
539
|
+
return errors
|
|
540
|
+
|
|
541
|
+
property_type = property_schema.property_type
|
|
542
|
+
|
|
543
|
+
# Check if property type supports nesting
|
|
544
|
+
# DICT type supports nesting, but we need nested schema
|
|
545
|
+
if hasattr(property_type, "value"):
|
|
546
|
+
type_value = property_type.value
|
|
547
|
+
elif hasattr(property_type, "name"):
|
|
548
|
+
type_value = property_type.name
|
|
549
|
+
else:
|
|
550
|
+
type_value = str(property_type)
|
|
551
|
+
|
|
552
|
+
# Import PropertyType to check if it's DICT
|
|
553
|
+
from aiecs.domain.knowledge_graph.schema.property_schema import PropertyType
|
|
554
|
+
|
|
555
|
+
if type_value == PropertyType.DICT.value or type_value == "dict":
|
|
556
|
+
# Property is DICT type - check for nested schema
|
|
557
|
+
nested_schema = self._get_nested_schema(property_schema)
|
|
558
|
+
if nested_schema is None:
|
|
559
|
+
# No nested schema defined - can't validate deeper nesting
|
|
560
|
+
errors.append(
|
|
561
|
+
ValidationError(
|
|
562
|
+
line=line,
|
|
563
|
+
column=column,
|
|
564
|
+
message=f"Cannot validate nested path '{full_path}.{remaining_path}': "
|
|
565
|
+
f"property '{current_property}' is DICT type but nested schema not defined",
|
|
566
|
+
suggestion=f"Define nested schema for '{current_property}' or use flat property path",
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
return errors
|
|
570
|
+
|
|
571
|
+
# Recursively validate remaining path
|
|
572
|
+
errors.extend(
|
|
573
|
+
self._validate_nested_property_path(
|
|
574
|
+
entity_schema=nested_schema,
|
|
575
|
+
property_path=remaining_path,
|
|
576
|
+
entity_type=entity_type,
|
|
577
|
+
line=line,
|
|
578
|
+
column=column,
|
|
579
|
+
current_path=full_path,
|
|
580
|
+
)
|
|
581
|
+
)
|
|
582
|
+
else:
|
|
583
|
+
# Property is not DICT type - can't nest further
|
|
584
|
+
errors.append(
|
|
585
|
+
ValidationError(
|
|
586
|
+
line=line,
|
|
587
|
+
column=column,
|
|
588
|
+
message=f"Cannot access nested path '{full_path}.{remaining_path}': "
|
|
589
|
+
f"property '{current_property}' is {type_value} type, not DICT",
|
|
590
|
+
suggestion=f"Use '{full_path}' directly or change property type to DICT",
|
|
591
|
+
)
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
return errors
|
|
595
|
+
|
|
596
|
+
def _get_nested_schema(self, property_schema: Any) -> Optional[Any]:
|
|
597
|
+
"""
|
|
598
|
+
Get nested schema for a DICT property
|
|
599
|
+
|
|
600
|
+
Checks for nested schema in multiple ways:
|
|
601
|
+
1. property_schema.nested_schema attribute
|
|
602
|
+
2. property_schema.schema attribute (if not a callable method)
|
|
603
|
+
3. property_schema.properties attribute (treat as EntityType-like)
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
property_schema: Property schema to get nested schema from
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
Nested schema object or None if not found
|
|
610
|
+
"""
|
|
611
|
+
# Check for explicit nested_schema attribute
|
|
612
|
+
if hasattr(property_schema, "nested_schema"):
|
|
613
|
+
nested_schema = getattr(property_schema, "nested_schema", None)
|
|
614
|
+
if nested_schema is not None:
|
|
615
|
+
return nested_schema
|
|
616
|
+
|
|
617
|
+
# Check for schema attribute (but not if it's a callable method)
|
|
618
|
+
if hasattr(property_schema, "schema"):
|
|
619
|
+
schema_attr = getattr(property_schema, "schema", None)
|
|
620
|
+
# Only use if it's not callable (Pydantic models have schema() method)
|
|
621
|
+
if schema_attr is not None and not callable(schema_attr):
|
|
622
|
+
return schema_attr
|
|
623
|
+
|
|
624
|
+
# Check if property_schema has properties attribute (treat as EntityType-like)
|
|
625
|
+
if hasattr(property_schema, "properties"):
|
|
626
|
+
properties = getattr(property_schema, "properties", None)
|
|
627
|
+
# Only use if it's a dict-like structure (not a Pydantic method)
|
|
628
|
+
if properties and isinstance(properties, dict) and len(properties) > 0:
|
|
629
|
+
# Create a mock entity schema-like object
|
|
630
|
+
class NestedSchema:
|
|
631
|
+
def __init__(self, properties):
|
|
632
|
+
self.properties = properties
|
|
633
|
+
|
|
634
|
+
def get_property(self, property_name: str):
|
|
635
|
+
if isinstance(self.properties, dict):
|
|
636
|
+
return self.properties.get(property_name)
|
|
637
|
+
return None
|
|
638
|
+
|
|
639
|
+
def get_property_names(self):
|
|
640
|
+
if isinstance(self.properties, dict):
|
|
641
|
+
return list(self.properties.keys())
|
|
642
|
+
return []
|
|
643
|
+
|
|
644
|
+
return NestedSchema(properties)
|
|
645
|
+
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
def validate_property_value_type(
|
|
649
|
+
self,
|
|
650
|
+
entity_type: str,
|
|
651
|
+
property_path: str,
|
|
652
|
+
value: Any,
|
|
653
|
+
operator: str,
|
|
654
|
+
line: int,
|
|
655
|
+
column: int,
|
|
656
|
+
) -> List[ValidationError]:
|
|
657
|
+
"""
|
|
658
|
+
Validate that a property value matches the expected type
|
|
659
|
+
|
|
660
|
+
Supports nested property paths by recursively finding the final property schema.
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
entity_type: Entity type name
|
|
664
|
+
property_path: Property path (may be nested like "address.city")
|
|
665
|
+
value: Value to validate
|
|
666
|
+
operator: Operator being used
|
|
667
|
+
line: Line number for error reporting
|
|
668
|
+
column: Column number for error reporting
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
List of validation errors
|
|
672
|
+
"""
|
|
673
|
+
errors: List[ValidationError] = []
|
|
674
|
+
|
|
675
|
+
# Get entity type schema
|
|
676
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
677
|
+
return errors
|
|
678
|
+
|
|
679
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
680
|
+
if entity_schema is None:
|
|
681
|
+
return errors
|
|
682
|
+
|
|
683
|
+
# Get property schema for nested path
|
|
684
|
+
property_schema = self._get_property_schema_for_path(entity_schema, property_path)
|
|
685
|
+
if property_schema is None:
|
|
686
|
+
return errors # Property error already reported
|
|
687
|
+
|
|
688
|
+
# Get property type
|
|
689
|
+
if not hasattr(property_schema, "property_type"):
|
|
690
|
+
return errors
|
|
691
|
+
|
|
692
|
+
property_type = property_schema.property_type
|
|
693
|
+
|
|
694
|
+
# Map property types to Python types
|
|
695
|
+
type_map = {
|
|
696
|
+
"STRING": str,
|
|
697
|
+
"INTEGER": int,
|
|
698
|
+
"FLOAT": float,
|
|
699
|
+
"BOOLEAN": bool,
|
|
700
|
+
"DATE": str, # Dates are typically strings in queries
|
|
701
|
+
"DATETIME": str,
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
# Get expected Python type
|
|
705
|
+
expected_type = None
|
|
706
|
+
if hasattr(property_type, "value"):
|
|
707
|
+
# Enum type
|
|
708
|
+
expected_type = type_map.get(property_type.value)
|
|
709
|
+
elif hasattr(property_type, "name"):
|
|
710
|
+
# String type name
|
|
711
|
+
expected_type = type_map.get(property_type.name)
|
|
712
|
+
elif isinstance(property_type, str):
|
|
713
|
+
# Direct string type
|
|
714
|
+
expected_type = type_map.get(property_type)
|
|
715
|
+
|
|
716
|
+
if expected_type is None:
|
|
717
|
+
return errors # Unknown type, skip validation
|
|
718
|
+
|
|
719
|
+
# For IN operator, check list elements
|
|
720
|
+
if operator == "IN":
|
|
721
|
+
if isinstance(value, list):
|
|
722
|
+
for item in value:
|
|
723
|
+
if not isinstance(item, expected_type):
|
|
724
|
+
errors.append(
|
|
725
|
+
ValidationError(
|
|
726
|
+
line=line,
|
|
727
|
+
column=column,
|
|
728
|
+
message=f"Property '{property_path}' expects {expected_type.__name__} values, "
|
|
729
|
+
f"but list contains {type(item).__name__}",
|
|
730
|
+
suggestion=f"Ensure all list values are {expected_type.__name__}",
|
|
731
|
+
)
|
|
732
|
+
)
|
|
733
|
+
break # Only report once
|
|
734
|
+
return errors
|
|
735
|
+
|
|
736
|
+
# Check value type
|
|
737
|
+
if not isinstance(value, expected_type):
|
|
738
|
+
errors.append(
|
|
739
|
+
ValidationError(
|
|
740
|
+
line=line,
|
|
741
|
+
column=column,
|
|
742
|
+
message=f"Property '{property_path}' expects {expected_type.__name__} value, "
|
|
743
|
+
f"got {type(value).__name__}",
|
|
744
|
+
suggestion=f"Use a {expected_type.__name__} value",
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
return errors
|
|
749
|
+
|
|
750
|
+
def _get_property_schema_for_path(self, entity_schema: Any, property_path: str) -> Optional[Any]:
|
|
751
|
+
"""
|
|
752
|
+
Get property schema for a nested property path
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
entity_schema: Entity schema to start from
|
|
756
|
+
property_path: Property path (may be nested like "address.city")
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
Property schema for the final property in the path, or None if not found
|
|
760
|
+
"""
|
|
761
|
+
property_parts = property_path.split(".")
|
|
762
|
+
current_schema = entity_schema
|
|
763
|
+
|
|
764
|
+
for i, part in enumerate(property_parts):
|
|
765
|
+
if not hasattr(current_schema, "get_property"):
|
|
766
|
+
return None
|
|
767
|
+
|
|
768
|
+
property_schema = current_schema.get_property(part)
|
|
769
|
+
if property_schema is None:
|
|
770
|
+
return None
|
|
771
|
+
|
|
772
|
+
# If this is the last part, return the property schema
|
|
773
|
+
if i == len(property_parts) - 1:
|
|
774
|
+
return property_schema
|
|
775
|
+
|
|
776
|
+
# Otherwise, check if this property supports nesting
|
|
777
|
+
if not hasattr(property_schema, "property_type"):
|
|
778
|
+
return None
|
|
779
|
+
|
|
780
|
+
property_type = property_schema.property_type
|
|
781
|
+
type_value = None
|
|
782
|
+
if hasattr(property_type, "value"):
|
|
783
|
+
type_value = property_type.value
|
|
784
|
+
elif hasattr(property_type, "name"):
|
|
785
|
+
type_value = property_type.name
|
|
786
|
+
else:
|
|
787
|
+
type_value = str(property_type)
|
|
788
|
+
|
|
789
|
+
# Import PropertyType to check if it's DICT
|
|
790
|
+
from aiecs.domain.knowledge_graph.schema.property_schema import PropertyType
|
|
791
|
+
|
|
792
|
+
if type_value == PropertyType.DICT.value or type_value == "dict":
|
|
793
|
+
# Get nested schema for next iteration
|
|
794
|
+
nested_schema = self._get_nested_schema(property_schema)
|
|
795
|
+
if nested_schema is None:
|
|
796
|
+
return None
|
|
797
|
+
current_schema = nested_schema
|
|
798
|
+
else:
|
|
799
|
+
# Can't nest further
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
return None
|
|
803
|
+
|
|
804
|
+
def validate_relation_endpoints(
|
|
805
|
+
self,
|
|
806
|
+
relation_type: str,
|
|
807
|
+
current_entity_type: str,
|
|
808
|
+
direction: str,
|
|
809
|
+
line: int,
|
|
810
|
+
column: int,
|
|
811
|
+
) -> List[ValidationError]:
|
|
812
|
+
"""
|
|
813
|
+
Validate that relation endpoints match entity types
|
|
814
|
+
|
|
815
|
+
Args:
|
|
816
|
+
relation_type: Relation type name
|
|
817
|
+
current_entity_type: Current entity type in the query
|
|
818
|
+
direction: Direction of traversal ("incoming" or "outgoing")
|
|
819
|
+
line: Line number for error reporting
|
|
820
|
+
column: Column number for error reporting
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
List of validation errors
|
|
824
|
+
"""
|
|
825
|
+
errors: List[ValidationError] = []
|
|
826
|
+
|
|
827
|
+
# Get relation type schema
|
|
828
|
+
if not hasattr(self.schema, "get_relation_type"):
|
|
829
|
+
return errors
|
|
830
|
+
|
|
831
|
+
relation_schema = self.schema.get_relation_type(relation_type)
|
|
832
|
+
if relation_schema is None:
|
|
833
|
+
return errors # Relation type error already reported
|
|
834
|
+
|
|
835
|
+
# Check if relation has endpoint constraints
|
|
836
|
+
if not hasattr(relation_schema, "source_entity_types") or not hasattr(relation_schema, "target_entity_types"):
|
|
837
|
+
return errors
|
|
838
|
+
|
|
839
|
+
source_types = relation_schema.source_entity_types
|
|
840
|
+
target_types = relation_schema.target_entity_types
|
|
841
|
+
|
|
842
|
+
# Validate based on direction
|
|
843
|
+
if direction == "outgoing":
|
|
844
|
+
# Current entity is source, check if it's allowed
|
|
845
|
+
if source_types and current_entity_type not in source_types:
|
|
846
|
+
errors.append(
|
|
847
|
+
ValidationError(
|
|
848
|
+
line=line,
|
|
849
|
+
column=column,
|
|
850
|
+
message=f"Entity type '{current_entity_type}' cannot be source of relation '{relation_type}'",
|
|
851
|
+
suggestion=f"Allowed source types: {', '.join(source_types)}",
|
|
852
|
+
)
|
|
853
|
+
)
|
|
854
|
+
elif direction == "incoming":
|
|
855
|
+
# Current entity is target, check if it's allowed
|
|
856
|
+
if target_types and current_entity_type not in target_types:
|
|
857
|
+
errors.append(
|
|
858
|
+
ValidationError(
|
|
859
|
+
line=line,
|
|
860
|
+
column=column,
|
|
861
|
+
message=f"Entity type '{current_entity_type}' cannot be target of relation '{relation_type}'",
|
|
862
|
+
suggestion=f"Allowed target types: {', '.join(target_types)}",
|
|
863
|
+
)
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
return errors
|