aiecs 1.0.1__py3-none-any.whl → 1.7.17__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 +435 -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 +3949 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1731 -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 +894 -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 +377 -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 +230 -37
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +328 -0
- aiecs/llm/clients/google_function_calling_mixin.py +415 -0
- aiecs/llm/clients/googleai_client.py +314 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +1186 -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 +1464 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1016 -0
- aiecs/tools/docs/document_writer_tool.py +2008 -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 +220 -141
- 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.17.dist-info}/METADATA +52 -15
- aiecs-1.7.17.dist-info/RECORD +337 -0
- aiecs-1.7.17.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.17.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tenant Context Infrastructure for Multi-Tenancy Support
|
|
3
|
+
|
|
4
|
+
Provides tenant isolation mechanisms and context management for knowledge graph storage.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TenantIsolationMode(str, Enum):
|
|
14
|
+
"""
|
|
15
|
+
Tenant isolation mode for multi-tenant deployments.
|
|
16
|
+
|
|
17
|
+
- DISABLED: No tenant isolation; all data in shared namespace (backward compatible)
|
|
18
|
+
- SHARED_SCHEMA: Shared database schema with tenant_id column filtering (PostgreSQL RLS, SQLite column filtering)
|
|
19
|
+
- SEPARATE_SCHEMA: Separate database schemas per tenant (PostgreSQL schemas, SQLite databases)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
DISABLED = "disabled"
|
|
23
|
+
SHARED_SCHEMA = "shared_schema"
|
|
24
|
+
SEPARATE_SCHEMA = "separate_schema"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TenantContext:
|
|
29
|
+
"""
|
|
30
|
+
Tenant context for multi-tenant operations.
|
|
31
|
+
|
|
32
|
+
Carries tenant identification and isolation mode through storage operations.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
tenant_id: Unique identifier for the tenant (alphanumeric, hyphens, underscores)
|
|
36
|
+
isolation_mode: Tenant isolation mode (default: SHARED_SCHEMA)
|
|
37
|
+
validate: Whether to validate tenant_id format (default: True)
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
```python
|
|
41
|
+
context = TenantContext(tenant_id="acme-corp", isolation_mode=TenantIsolationMode.SHARED_SCHEMA)
|
|
42
|
+
await graph_store.add_entity(entity, context=context)
|
|
43
|
+
```
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
tenant_id: str
|
|
47
|
+
isolation_mode: TenantIsolationMode = TenantIsolationMode.SHARED_SCHEMA
|
|
48
|
+
validate: bool = True
|
|
49
|
+
|
|
50
|
+
def __post_init__(self) -> None:
|
|
51
|
+
"""Validate tenant_id format if validation is enabled."""
|
|
52
|
+
if self.validate:
|
|
53
|
+
validate_tenant_id(self.tenant_id)
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return f"TenantContext(tenant_id={self.tenant_id}, mode={self.isolation_mode.value})"
|
|
57
|
+
|
|
58
|
+
def __repr__(self) -> str:
|
|
59
|
+
return (
|
|
60
|
+
f"TenantContext(tenant_id='{self.tenant_id}', "
|
|
61
|
+
f"isolation_mode=TenantIsolationMode.{self.isolation_mode.name})"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InvalidTenantIdError(ValueError):
|
|
66
|
+
"""
|
|
67
|
+
Raised when tenant_id format is invalid.
|
|
68
|
+
|
|
69
|
+
Tenant IDs must be alphanumeric strings with hyphens and underscores only.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, tenant_id: str, reason: str = ""):
|
|
73
|
+
message = f"Invalid tenant_id format: '{tenant_id}'"
|
|
74
|
+
if reason:
|
|
75
|
+
message += f". {reason}"
|
|
76
|
+
super().__init__(message)
|
|
77
|
+
self.tenant_id = tenant_id
|
|
78
|
+
self.reason = reason
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CrossTenantRelationError(ValueError):
|
|
82
|
+
"""
|
|
83
|
+
Raised when attempting to create a relation between entities from different tenants.
|
|
84
|
+
|
|
85
|
+
Relations must always be within the same tenant scope.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, source_tenant: Optional[str], target_tenant: Optional[str]):
|
|
89
|
+
message = (
|
|
90
|
+
f"Cannot create relation across tenants: "
|
|
91
|
+
f"source_tenant={source_tenant}, target_tenant={target_tenant}"
|
|
92
|
+
)
|
|
93
|
+
super().__init__(message)
|
|
94
|
+
self.source_tenant = source_tenant
|
|
95
|
+
self.target_tenant = target_tenant
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CrossTenantFusionError(ValueError):
|
|
99
|
+
"""
|
|
100
|
+
Raised when attempting to fuse entities from different tenants.
|
|
101
|
+
|
|
102
|
+
Knowledge fusion operations must always be within the same tenant scope.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, tenant_ids: set):
|
|
106
|
+
message = (
|
|
107
|
+
f"Cannot fuse entities across tenants. "
|
|
108
|
+
f"Found entities from multiple tenants: {sorted(tenant_ids)}"
|
|
109
|
+
)
|
|
110
|
+
super().__init__(message)
|
|
111
|
+
self.tenant_ids = tenant_ids
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TenantContextRequiredError(ValueError):
|
|
115
|
+
"""
|
|
116
|
+
Raised when a tenant context is required but not provided.
|
|
117
|
+
|
|
118
|
+
In multi-tenant mode, certain operations require explicit tenant context.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, operation: str = "operation"):
|
|
122
|
+
message = (
|
|
123
|
+
f"TenantContext is required for {operation} in multi-tenant mode. "
|
|
124
|
+
f"Please provide a TenantContext with tenant_id."
|
|
125
|
+
)
|
|
126
|
+
super().__init__(message)
|
|
127
|
+
self.operation = operation
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Tenant ID validation pattern: alphanumeric, hyphens, underscores only
|
|
131
|
+
TENANT_ID_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
132
|
+
TENANT_ID_MIN_LENGTH = 1
|
|
133
|
+
TENANT_ID_MAX_LENGTH = 255
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def validate_tenant_id(tenant_id: str) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Validate tenant_id format.
|
|
139
|
+
|
|
140
|
+
Tenant IDs must:
|
|
141
|
+
- Be non-empty strings
|
|
142
|
+
- Contain only alphanumeric characters, hyphens, and underscores
|
|
143
|
+
- Be between 1 and 255 characters
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
tenant_id: Tenant ID to validate
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Validated tenant_id (unchanged if valid)
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
InvalidTenantIdError: If tenant_id format is invalid
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
```python
|
|
156
|
+
validate_tenant_id("acme-corp") # OK
|
|
157
|
+
validate_tenant_id("acme_corp_123") # OK
|
|
158
|
+
validate_tenant_id("acme@corp") # Raises InvalidTenantIdError
|
|
159
|
+
```
|
|
160
|
+
"""
|
|
161
|
+
if not isinstance(tenant_id, str):
|
|
162
|
+
raise InvalidTenantIdError(
|
|
163
|
+
str(tenant_id), reason="tenant_id must be a string"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if not tenant_id:
|
|
167
|
+
raise InvalidTenantIdError(
|
|
168
|
+
tenant_id, reason="tenant_id cannot be empty"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if len(tenant_id) < TENANT_ID_MIN_LENGTH:
|
|
172
|
+
raise InvalidTenantIdError(
|
|
173
|
+
tenant_id,
|
|
174
|
+
reason=f"tenant_id must be at least {TENANT_ID_MIN_LENGTH} character(s)",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if len(tenant_id) > TENANT_ID_MAX_LENGTH:
|
|
178
|
+
raise InvalidTenantIdError(
|
|
179
|
+
tenant_id,
|
|
180
|
+
reason=f"tenant_id must be at most {TENANT_ID_MAX_LENGTH} characters",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not TENANT_ID_PATTERN.match(tenant_id):
|
|
184
|
+
raise InvalidTenantIdError(
|
|
185
|
+
tenant_id,
|
|
186
|
+
reason="tenant_id can only contain alphanumeric characters, hyphens, and underscores",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return tenant_id
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def normalize_tenant_id(tenant_id: Optional[str]) -> Optional[str]:
|
|
193
|
+
"""
|
|
194
|
+
Normalize tenant_id (strip whitespace, convert to lowercase if needed).
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
tenant_id: Tenant ID to normalize
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Normalized tenant_id or None if input is None/empty
|
|
201
|
+
|
|
202
|
+
Note:
|
|
203
|
+
This function does not validate format. Use validate_tenant_id() for validation.
|
|
204
|
+
"""
|
|
205
|
+
if tenant_id is None:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
normalized = tenant_id.strip()
|
|
209
|
+
if not normalized:
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
return normalized
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TenantAwareStorageResolver:
|
|
216
|
+
"""
|
|
217
|
+
Resolves storage targets based on tenant context and isolation mode.
|
|
218
|
+
|
|
219
|
+
This class provides unified path resolution for all GraphStore implementations,
|
|
220
|
+
ensuring consistent tenant routing regardless of backend storage type.
|
|
221
|
+
|
|
222
|
+
The resolver handles:
|
|
223
|
+
- Table name resolution (base table, prefixed tables for separate-schema mode)
|
|
224
|
+
- Connection/schema routing (PostgreSQL schemas, SQLite database files)
|
|
225
|
+
- Tenant validation and normalization
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
```python
|
|
229
|
+
resolver = TenantAwareStorageResolver()
|
|
230
|
+
|
|
231
|
+
# SHARED_SCHEMA mode: returns base table name
|
|
232
|
+
context = TenantContext(tenant_id="acme", isolation_mode=TenantIsolationMode.SHARED_SCHEMA)
|
|
233
|
+
table = resolver.resolve_table_name("entities", context) # "entities"
|
|
234
|
+
|
|
235
|
+
# SEPARATE_SCHEMA mode: returns prefixed table name (for SQLite)
|
|
236
|
+
context = TenantContext(tenant_id="acme", isolation_mode=TenantIsolationMode.SEPARATE_SCHEMA)
|
|
237
|
+
table = resolver.resolve_table_name("entities", context) # "tenant_acme_entities"
|
|
238
|
+
|
|
239
|
+
# Schema name resolution for PostgreSQL SEPARATE_SCHEMA mode
|
|
240
|
+
schema = resolver.resolve_schema_name(context) # "tenant_acme"
|
|
241
|
+
```
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def __init__(self, table_prefix: str = "tenant", schema_prefix: str = "tenant"):
|
|
245
|
+
"""
|
|
246
|
+
Initialize storage resolver.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
table_prefix: Prefix for tenant-specific tables in SEPARATE_SCHEMA mode (default: "tenant")
|
|
250
|
+
schema_prefix: Prefix for tenant-specific PostgreSQL schemas (default: "tenant")
|
|
251
|
+
"""
|
|
252
|
+
self.table_prefix = table_prefix
|
|
253
|
+
self.schema_prefix = schema_prefix
|
|
254
|
+
|
|
255
|
+
def resolve_table_name(
|
|
256
|
+
self,
|
|
257
|
+
base_table: str,
|
|
258
|
+
context: Optional[TenantContext] = None
|
|
259
|
+
) -> str:
|
|
260
|
+
"""
|
|
261
|
+
Resolve table name based on tenant context and isolation mode.
|
|
262
|
+
|
|
263
|
+
Resolution logic:
|
|
264
|
+
- DISABLED or None context: returns base_table
|
|
265
|
+
- SHARED_SCHEMA: returns base_table (filtering by tenant_id column)
|
|
266
|
+
- SEPARATE_SCHEMA: returns "{prefix}_{tenant_id}_{base_table}"
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
base_table: Base table name (e.g., "entities", "relations")
|
|
270
|
+
context: Optional tenant context
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Resolved table name
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
```python
|
|
277
|
+
resolver.resolve_table_name("entities", None) # "entities"
|
|
278
|
+
|
|
279
|
+
ctx = TenantContext("acme", TenantIsolationMode.SHARED_SCHEMA)
|
|
280
|
+
resolver.resolve_table_name("entities", ctx) # "entities"
|
|
281
|
+
|
|
282
|
+
ctx = TenantContext("acme", TenantIsolationMode.SEPARATE_SCHEMA)
|
|
283
|
+
resolver.resolve_table_name("entities", ctx) # "tenant_acme_entities"
|
|
284
|
+
```
|
|
285
|
+
"""
|
|
286
|
+
if context is None or context.isolation_mode == TenantIsolationMode.DISABLED:
|
|
287
|
+
return base_table
|
|
288
|
+
|
|
289
|
+
if context.isolation_mode == TenantIsolationMode.SHARED_SCHEMA:
|
|
290
|
+
# Use base table with tenant_id column filtering
|
|
291
|
+
return base_table
|
|
292
|
+
|
|
293
|
+
if context.isolation_mode == TenantIsolationMode.SEPARATE_SCHEMA:
|
|
294
|
+
# Use tenant-prefixed table name
|
|
295
|
+
return f"{self.table_prefix}_{context.tenant_id}_{base_table}"
|
|
296
|
+
|
|
297
|
+
return base_table
|
|
298
|
+
|
|
299
|
+
def resolve_schema_name(self, context: Optional[TenantContext] = None) -> Optional[str]:
|
|
300
|
+
"""
|
|
301
|
+
Resolve PostgreSQL schema name for SEPARATE_SCHEMA mode.
|
|
302
|
+
|
|
303
|
+
Returns schema name for PostgreSQL search_path configuration.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
context: Optional tenant context
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Schema name (e.g., "tenant_acme") or None if not in SEPARATE_SCHEMA mode
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
```python
|
|
313
|
+
ctx = TenantContext("acme", TenantIsolationMode.SEPARATE_SCHEMA)
|
|
314
|
+
schema = resolver.resolve_schema_name(ctx) # "tenant_acme"
|
|
315
|
+
|
|
316
|
+
ctx = TenantContext("acme", TenantIsolationMode.SHARED_SCHEMA)
|
|
317
|
+
schema = resolver.resolve_schema_name(ctx) # None
|
|
318
|
+
```
|
|
319
|
+
"""
|
|
320
|
+
if context is None or context.isolation_mode != TenantIsolationMode.SEPARATE_SCHEMA:
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
return f"{self.schema_prefix}_{context.tenant_id}"
|
|
324
|
+
|
|
325
|
+
def resolve_database_path(
|
|
326
|
+
self,
|
|
327
|
+
base_path: str,
|
|
328
|
+
context: Optional[TenantContext] = None
|
|
329
|
+
) -> str:
|
|
330
|
+
"""
|
|
331
|
+
Resolve SQLite database file path for SEPARATE_SCHEMA mode.
|
|
332
|
+
|
|
333
|
+
In SEPARATE_SCHEMA mode, each tenant gets its own SQLite database file.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
base_path: Base database path (e.g., "/data/graph.db")
|
|
337
|
+
context: Optional tenant context
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
Resolved database path
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
```python
|
|
344
|
+
ctx = TenantContext("acme", TenantIsolationMode.SEPARATE_SCHEMA)
|
|
345
|
+
path = resolver.resolve_database_path("/data/graph.db", ctx)
|
|
346
|
+
# "/data/tenant_acme.db"
|
|
347
|
+
|
|
348
|
+
ctx = TenantContext("acme", TenantIsolationMode.SHARED_SCHEMA)
|
|
349
|
+
path = resolver.resolve_database_path("/data/graph.db", ctx)
|
|
350
|
+
# "/data/graph.db"
|
|
351
|
+
```
|
|
352
|
+
"""
|
|
353
|
+
if context is None or context.isolation_mode != TenantIsolationMode.SEPARATE_SCHEMA:
|
|
354
|
+
return base_path
|
|
355
|
+
|
|
356
|
+
# Extract directory and base name from path
|
|
357
|
+
import os
|
|
358
|
+
directory = os.path.dirname(base_path)
|
|
359
|
+
# Generate tenant-specific database file
|
|
360
|
+
tenant_db = f"{self.table_prefix}_{context.tenant_id}.db"
|
|
361
|
+
|
|
362
|
+
if directory:
|
|
363
|
+
return os.path.join(directory, tenant_db)
|
|
364
|
+
return tenant_db
|
|
365
|
+
|
|
366
|
+
def should_filter_by_tenant_id(self, context: Optional[TenantContext] = None) -> bool:
|
|
367
|
+
"""
|
|
368
|
+
Determine if queries should include tenant_id column filter.
|
|
369
|
+
|
|
370
|
+
Returns True for SHARED_SCHEMA mode, False for SEPARATE_SCHEMA or DISABLED.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
context: Optional tenant context
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
True if queries should filter by tenant_id column, False otherwise
|
|
377
|
+
|
|
378
|
+
Example:
|
|
379
|
+
```python
|
|
380
|
+
ctx = TenantContext("acme", TenantIsolationMode.SHARED_SCHEMA)
|
|
381
|
+
resolver.should_filter_by_tenant_id(ctx) # True
|
|
382
|
+
|
|
383
|
+
ctx = TenantContext("acme", TenantIsolationMode.SEPARATE_SCHEMA)
|
|
384
|
+
resolver.should_filter_by_tenant_id(ctx) # False (separate storage)
|
|
385
|
+
```
|
|
386
|
+
"""
|
|
387
|
+
if context is None or context.isolation_mode == TenantIsolationMode.DISABLED:
|
|
388
|
+
return False
|
|
389
|
+
|
|
390
|
+
return context.isolation_mode == TenantIsolationMode.SHARED_SCHEMA
|
|
391
|
+
|
|
392
|
+
def validate_context(self, context: Optional[TenantContext]) -> None:
|
|
393
|
+
"""
|
|
394
|
+
Validate tenant context format and configuration.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
context: Tenant context to validate
|
|
398
|
+
|
|
399
|
+
Raises:
|
|
400
|
+
InvalidTenantIdError: If tenant_id format is invalid
|
|
401
|
+
|
|
402
|
+
Example:
|
|
403
|
+
```python
|
|
404
|
+
ctx = TenantContext("acme-corp", TenantIsolationMode.SHARED_SCHEMA)
|
|
405
|
+
resolver.validate_context(ctx) # OK
|
|
406
|
+
|
|
407
|
+
ctx = TenantContext("acme@corp", TenantIsolationMode.SHARED_SCHEMA)
|
|
408
|
+
resolver.validate_context(ctx) # Raises InvalidTenantIdError
|
|
409
|
+
```
|
|
410
|
+
"""
|
|
411
|
+
if context is not None:
|
|
412
|
+
validate_tenant_id(context.tenant_id)
|