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,597 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alias Index
|
|
3
|
+
|
|
4
|
+
Provides O(1) lookup for entity aliases and abbreviations.
|
|
5
|
+
Supports in-memory HashMap and Redis backends with tenant isolation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Dict, List, Optional, Set, AsyncIterator, Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MatchType(Enum):
|
|
20
|
+
"""Type of alias match"""
|
|
21
|
+
EXACT = "exact"
|
|
22
|
+
ALIAS = "alias"
|
|
23
|
+
ABBREVIATION = "abbreviation"
|
|
24
|
+
NORMALIZED = "normalized"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class AliasEntry:
|
|
29
|
+
"""Entry in the alias index"""
|
|
30
|
+
entity_id: str
|
|
31
|
+
match_type: MatchType
|
|
32
|
+
original_name: Optional[str] = None # Original form before normalization
|
|
33
|
+
tenant_id: Optional[str] = None # Tenant ID for multi-tenant isolation
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TransactionState:
|
|
38
|
+
"""State for tracking transaction operations"""
|
|
39
|
+
operations: List[tuple] = field(default_factory=list) # (op_type, key, value)
|
|
40
|
+
committed: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AliasIndexBackend(ABC):
|
|
44
|
+
"""Abstract base class for alias index backends"""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
async def get(self, alias: str) -> Optional[AliasEntry]:
|
|
48
|
+
"""Get entity entry for an alias"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
async def set(self, alias: str, entry: AliasEntry) -> None:
|
|
53
|
+
"""Set alias to entity mapping"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def delete(self, alias: str) -> bool:
|
|
58
|
+
"""Delete an alias entry. Returns True if existed."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def get_by_entity_id(self, entity_id: str) -> List[str]:
|
|
63
|
+
"""Get all aliases for an entity"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
async def clear(self) -> None:
|
|
68
|
+
"""Clear all entries"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
async def size(self) -> int:
|
|
73
|
+
"""Get number of entries"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
@asynccontextmanager
|
|
78
|
+
async def transaction(self) -> AsyncIterator["TransactionContext"]:
|
|
79
|
+
"""Start a transaction for atomic operations"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TransactionContext:
|
|
84
|
+
"""Context for transactional operations"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, backend: AliasIndexBackend):
|
|
87
|
+
self.backend = backend
|
|
88
|
+
self.state = TransactionState()
|
|
89
|
+
self._rollback_data: Dict[str, Optional[AliasEntry]] = {}
|
|
90
|
+
|
|
91
|
+
async def set(self, alias: str, entry: AliasEntry) -> None:
|
|
92
|
+
"""Set within transaction"""
|
|
93
|
+
# Store current value for rollback
|
|
94
|
+
if alias not in self._rollback_data:
|
|
95
|
+
self._rollback_data[alias] = await self.backend.get(alias)
|
|
96
|
+
self.state.operations.append(("set", alias, entry))
|
|
97
|
+
await self.backend.set(alias, entry)
|
|
98
|
+
|
|
99
|
+
async def delete(self, alias: str) -> bool:
|
|
100
|
+
"""Delete within transaction"""
|
|
101
|
+
# Store current value for rollback
|
|
102
|
+
if alias not in self._rollback_data:
|
|
103
|
+
self._rollback_data[alias] = await self.backend.get(alias)
|
|
104
|
+
self.state.operations.append(("delete", alias, None))
|
|
105
|
+
return await self.backend.delete(alias)
|
|
106
|
+
|
|
107
|
+
async def rollback(self) -> None:
|
|
108
|
+
"""Rollback all operations in this transaction"""
|
|
109
|
+
for alias, original_entry in self._rollback_data.items():
|
|
110
|
+
if original_entry is None:
|
|
111
|
+
await self.backend.delete(alias)
|
|
112
|
+
else:
|
|
113
|
+
await self.backend.set(alias, original_entry)
|
|
114
|
+
self.state.operations.clear()
|
|
115
|
+
self._rollback_data.clear()
|
|
116
|
+
logger.debug("Transaction rolled back")
|
|
117
|
+
|
|
118
|
+
def commit(self) -> None:
|
|
119
|
+
"""Mark transaction as committed"""
|
|
120
|
+
self.state.committed = True
|
|
121
|
+
self._rollback_data.clear()
|
|
122
|
+
logger.debug(f"Transaction committed with {len(self.state.operations)} operations")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class InMemoryBackend(AliasIndexBackend):
|
|
126
|
+
"""In-memory HashMap backend for O(1) lookup with tenant isolation"""
|
|
127
|
+
|
|
128
|
+
def __init__(self):
|
|
129
|
+
self._index: Dict[str, AliasEntry] = {}
|
|
130
|
+
self._entity_aliases: Dict[str, Set[str]] = {} # entity_id -> set of aliases
|
|
131
|
+
self._lock = asyncio.Lock()
|
|
132
|
+
|
|
133
|
+
def _make_key(self, alias: str, tenant_id: Optional[str] = None) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Create tenant-prefixed key for alias lookup.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
alias: The alias string
|
|
139
|
+
tenant_id: Optional tenant ID for multi-tenant isolation
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Tenant-prefixed key (e.g., "tenant_123:apple" or "apple" for global)
|
|
143
|
+
"""
|
|
144
|
+
normalized = alias.lower()
|
|
145
|
+
if tenant_id:
|
|
146
|
+
return f"{tenant_id}:{normalized}"
|
|
147
|
+
return normalized
|
|
148
|
+
|
|
149
|
+
async def get(self, alias: str) -> Optional[AliasEntry]:
|
|
150
|
+
"""Get entity entry for an alias - O(1)"""
|
|
151
|
+
# For backward compatibility, try both with and without tenant prefix
|
|
152
|
+
# First try as-is (may contain tenant prefix already)
|
|
153
|
+
entry = self._index.get(alias.lower())
|
|
154
|
+
if entry:
|
|
155
|
+
return entry
|
|
156
|
+
# If alias contains ":", it might already be prefixed
|
|
157
|
+
if ":" in alias:
|
|
158
|
+
return self._index.get(alias.lower())
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
async def set(self, alias: str, entry: AliasEntry) -> None:
|
|
162
|
+
"""Set alias to entity mapping - O(1)"""
|
|
163
|
+
key = self._make_key(alias, entry.tenant_id)
|
|
164
|
+
self._index[key] = entry
|
|
165
|
+
# Track reverse mapping
|
|
166
|
+
if entry.entity_id not in self._entity_aliases:
|
|
167
|
+
self._entity_aliases[entry.entity_id] = set()
|
|
168
|
+
self._entity_aliases[entry.entity_id].add(key)
|
|
169
|
+
|
|
170
|
+
async def delete(self, alias: str) -> bool:
|
|
171
|
+
"""Delete an alias entry - O(1)"""
|
|
172
|
+
key = alias.lower()
|
|
173
|
+
if key in self._index:
|
|
174
|
+
entry = self._index.pop(key)
|
|
175
|
+
# Update reverse mapping
|
|
176
|
+
if entry.entity_id in self._entity_aliases:
|
|
177
|
+
self._entity_aliases[entry.entity_id].discard(key)
|
|
178
|
+
return True
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
async def get_by_entity_id(self, entity_id: str) -> List[str]:
|
|
182
|
+
"""Get all aliases for an entity - O(1)"""
|
|
183
|
+
return list(self._entity_aliases.get(entity_id, set()))
|
|
184
|
+
|
|
185
|
+
async def clear(self) -> None:
|
|
186
|
+
"""Clear all entries"""
|
|
187
|
+
self._index.clear()
|
|
188
|
+
self._entity_aliases.clear()
|
|
189
|
+
|
|
190
|
+
async def size(self) -> int:
|
|
191
|
+
"""Get number of entries"""
|
|
192
|
+
return len(self._index)
|
|
193
|
+
|
|
194
|
+
@asynccontextmanager
|
|
195
|
+
async def transaction(self) -> AsyncIterator[TransactionContext]:
|
|
196
|
+
"""Start a transaction with lock for concurrency safety"""
|
|
197
|
+
async with self._lock:
|
|
198
|
+
ctx = TransactionContext(self)
|
|
199
|
+
try:
|
|
200
|
+
yield ctx
|
|
201
|
+
ctx.commit()
|
|
202
|
+
except Exception:
|
|
203
|
+
await ctx.rollback()
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class RedisBackend(AliasIndexBackend):
|
|
208
|
+
"""
|
|
209
|
+
Redis backend for large graphs or distributed deployments.
|
|
210
|
+
|
|
211
|
+
Uses Redis hashes for O(1) lookup and MULTI/EXEC for atomic transactions.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# Redis key prefixes
|
|
215
|
+
ALIAS_KEY = "alias_index:aliases" # Hash: alias -> JSON(AliasEntry)
|
|
216
|
+
ENTITY_KEY_PREFIX = "alias_index:entity:" # Set: entity_id -> aliases
|
|
217
|
+
|
|
218
|
+
def __init__(self, redis_client: Any = None, redis_url: Optional[str] = None):
|
|
219
|
+
"""
|
|
220
|
+
Initialize Redis backend.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
redis_client: Existing Redis client (async)
|
|
224
|
+
redis_url: Redis URL for creating new client
|
|
225
|
+
"""
|
|
226
|
+
self._client = redis_client
|
|
227
|
+
self._redis_url = redis_url
|
|
228
|
+
self._initialized = False
|
|
229
|
+
|
|
230
|
+
async def _ensure_client(self) -> None:
|
|
231
|
+
"""Ensure Redis client is initialized"""
|
|
232
|
+
if self._initialized:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
if self._client is None:
|
|
236
|
+
try:
|
|
237
|
+
import redis.asyncio as redis
|
|
238
|
+
self._client = redis.from_url(
|
|
239
|
+
self._redis_url or "redis://localhost:6379",
|
|
240
|
+
decode_responses=True
|
|
241
|
+
)
|
|
242
|
+
except ImportError:
|
|
243
|
+
raise ImportError(
|
|
244
|
+
"redis package required for Redis backend. "
|
|
245
|
+
"Install with: pip install redis"
|
|
246
|
+
)
|
|
247
|
+
self._initialized = True
|
|
248
|
+
|
|
249
|
+
def _make_key(self, alias: str, tenant_id: Optional[str] = None) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Create tenant-prefixed key for alias lookup.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
alias: The alias string
|
|
255
|
+
tenant_id: Optional tenant ID for multi-tenant isolation
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Tenant-prefixed key (e.g., "tenant_123:apple" or "apple" for global)
|
|
259
|
+
"""
|
|
260
|
+
normalized = alias.lower()
|
|
261
|
+
if tenant_id:
|
|
262
|
+
return f"{tenant_id}:{normalized}"
|
|
263
|
+
return normalized
|
|
264
|
+
|
|
265
|
+
async def get(self, alias: str) -> Optional[AliasEntry]:
|
|
266
|
+
"""Get entity entry for an alias - O(1)"""
|
|
267
|
+
await self._ensure_client()
|
|
268
|
+
import json
|
|
269
|
+
|
|
270
|
+
# Try to get with the key as-is (may already be prefixed)
|
|
271
|
+
data = await self._client.hget(self.ALIAS_KEY, alias.lower())
|
|
272
|
+
if data:
|
|
273
|
+
entry_dict = json.loads(data)
|
|
274
|
+
return AliasEntry(
|
|
275
|
+
entity_id=entry_dict["entity_id"],
|
|
276
|
+
match_type=MatchType(entry_dict["match_type"]),
|
|
277
|
+
original_name=entry_dict.get("original_name"),
|
|
278
|
+
tenant_id=entry_dict.get("tenant_id")
|
|
279
|
+
)
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
async def set(self, alias: str, entry: AliasEntry) -> None:
|
|
283
|
+
"""Set alias to entity mapping - O(1)"""
|
|
284
|
+
await self._ensure_client()
|
|
285
|
+
import json
|
|
286
|
+
|
|
287
|
+
key = self._make_key(alias, entry.tenant_id)
|
|
288
|
+
entry_dict = {
|
|
289
|
+
"entity_id": entry.entity_id,
|
|
290
|
+
"match_type": entry.match_type.value,
|
|
291
|
+
"original_name": entry.original_name,
|
|
292
|
+
"tenant_id": entry.tenant_id
|
|
293
|
+
}
|
|
294
|
+
await self._client.hset(self.ALIAS_KEY, key, json.dumps(entry_dict))
|
|
295
|
+
# Track reverse mapping
|
|
296
|
+
await self._client.sadd(f"{self.ENTITY_KEY_PREFIX}{entry.entity_id}", key)
|
|
297
|
+
|
|
298
|
+
async def delete(self, alias: str) -> bool:
|
|
299
|
+
"""Delete an alias entry - O(1)"""
|
|
300
|
+
await self._ensure_client()
|
|
301
|
+
|
|
302
|
+
key = alias.lower()
|
|
303
|
+
# Get entry first to update reverse mapping
|
|
304
|
+
entry = await self.get(alias)
|
|
305
|
+
if entry:
|
|
306
|
+
await self._client.hdel(self.ALIAS_KEY, key)
|
|
307
|
+
await self._client.srem(f"{self.ENTITY_KEY_PREFIX}{entry.entity_id}", key)
|
|
308
|
+
return True
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
async def get_by_entity_id(self, entity_id: str) -> List[str]:
|
|
312
|
+
"""Get all aliases for an entity - O(1)"""
|
|
313
|
+
await self._ensure_client()
|
|
314
|
+
members = await self._client.smembers(f"{self.ENTITY_KEY_PREFIX}{entity_id}")
|
|
315
|
+
return list(members) if members else []
|
|
316
|
+
|
|
317
|
+
async def clear(self) -> None:
|
|
318
|
+
"""Clear all entries"""
|
|
319
|
+
await self._ensure_client()
|
|
320
|
+
# Get all entity keys to delete
|
|
321
|
+
cursor = 0
|
|
322
|
+
keys_to_delete = [self.ALIAS_KEY]
|
|
323
|
+
while True:
|
|
324
|
+
cursor, keys = await self._client.scan(
|
|
325
|
+
cursor, match=f"{self.ENTITY_KEY_PREFIX}*", count=100
|
|
326
|
+
)
|
|
327
|
+
keys_to_delete.extend(keys)
|
|
328
|
+
if cursor == 0:
|
|
329
|
+
break
|
|
330
|
+
if keys_to_delete:
|
|
331
|
+
await self._client.delete(*keys_to_delete)
|
|
332
|
+
|
|
333
|
+
async def size(self) -> int:
|
|
334
|
+
"""Get number of entries"""
|
|
335
|
+
await self._ensure_client()
|
|
336
|
+
return await self._client.hlen(self.ALIAS_KEY)
|
|
337
|
+
|
|
338
|
+
@asynccontextmanager
|
|
339
|
+
async def transaction(self) -> AsyncIterator[TransactionContext]:
|
|
340
|
+
"""
|
|
341
|
+
Start a Redis transaction using MULTI/EXEC.
|
|
342
|
+
|
|
343
|
+
Note: For true atomicity, operations are collected and executed
|
|
344
|
+
together. Rollback restores previous state.
|
|
345
|
+
"""
|
|
346
|
+
await self._ensure_client()
|
|
347
|
+
ctx = TransactionContext(self)
|
|
348
|
+
try:
|
|
349
|
+
yield ctx
|
|
350
|
+
ctx.commit()
|
|
351
|
+
except Exception:
|
|
352
|
+
await ctx.rollback()
|
|
353
|
+
raise
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class AliasIndex:
|
|
357
|
+
"""
|
|
358
|
+
High-level alias index with O(1) lookup.
|
|
359
|
+
|
|
360
|
+
Supports automatic backend selection based on graph size:
|
|
361
|
+
- In-memory HashMap for small-medium graphs (<100K entities)
|
|
362
|
+
- Redis for large graphs or distributed deployments
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
```python
|
|
366
|
+
# Create with auto-detection
|
|
367
|
+
index = AliasIndex()
|
|
368
|
+
|
|
369
|
+
# Or specify backend
|
|
370
|
+
index = AliasIndex(backend="redis", redis_url="redis://localhost:6379")
|
|
371
|
+
|
|
372
|
+
# Add aliases
|
|
373
|
+
await index.add_alias("Albert Einstein", "person_123", MatchType.EXACT)
|
|
374
|
+
await index.add_alias("A. Einstein", "person_123", MatchType.ALIAS)
|
|
375
|
+
await index.add_alias("Einstein", "person_123", MatchType.ALIAS)
|
|
376
|
+
|
|
377
|
+
# Lookup - O(1)
|
|
378
|
+
entry = await index.lookup("a. einstein")
|
|
379
|
+
assert entry.entity_id == "person_123"
|
|
380
|
+
|
|
381
|
+
# Atomic merge
|
|
382
|
+
async with index.transaction() as tx:
|
|
383
|
+
await tx.delete("old_alias")
|
|
384
|
+
await tx.set("new_alias", AliasEntry(...))
|
|
385
|
+
```
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
# Threshold for auto-switching to Redis backend
|
|
389
|
+
AUTO_REDIS_THRESHOLD = 100_000
|
|
390
|
+
|
|
391
|
+
def __init__(
|
|
392
|
+
self,
|
|
393
|
+
backend: Optional[str] = None,
|
|
394
|
+
redis_url: Optional[str] = None,
|
|
395
|
+
redis_client: Any = None,
|
|
396
|
+
auto_redis_threshold: int = AUTO_REDIS_THRESHOLD,
|
|
397
|
+
):
|
|
398
|
+
"""
|
|
399
|
+
Initialize alias index.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
backend: "memory" or "redis". If None, auto-detects.
|
|
403
|
+
redis_url: Redis URL for Redis backend
|
|
404
|
+
redis_client: Existing Redis client
|
|
405
|
+
auto_redis_threshold: Entity count threshold for auto-switching to Redis
|
|
406
|
+
"""
|
|
407
|
+
self._backend_type = backend
|
|
408
|
+
self._redis_url = redis_url
|
|
409
|
+
self._redis_client = redis_client
|
|
410
|
+
self._auto_redis_threshold = auto_redis_threshold
|
|
411
|
+
self._backend: Optional[AliasIndexBackend] = None
|
|
412
|
+
self._lock = asyncio.Lock()
|
|
413
|
+
|
|
414
|
+
async def _get_backend(self) -> AliasIndexBackend:
|
|
415
|
+
"""Get or create backend"""
|
|
416
|
+
if self._backend is None:
|
|
417
|
+
if self._backend_type == "redis":
|
|
418
|
+
self._backend = RedisBackend(
|
|
419
|
+
redis_client=self._redis_client,
|
|
420
|
+
redis_url=self._redis_url
|
|
421
|
+
)
|
|
422
|
+
else:
|
|
423
|
+
# Default to in-memory
|
|
424
|
+
self._backend = InMemoryBackend()
|
|
425
|
+
return self._backend
|
|
426
|
+
|
|
427
|
+
async def lookup(
|
|
428
|
+
self, alias: str, tenant_id: Optional[str] = None
|
|
429
|
+
) -> Optional[AliasEntry]:
|
|
430
|
+
"""
|
|
431
|
+
Look up an alias - O(1).
|
|
432
|
+
|
|
433
|
+
**Tenant Isolation**: When tenant_id is provided, lookup uses tenant-prefixed
|
|
434
|
+
keys to ensure isolation between tenants.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
alias: The alias to look up (case-insensitive)
|
|
438
|
+
tenant_id: Optional tenant ID for multi-tenant isolation
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
AliasEntry if found, None otherwise
|
|
442
|
+
"""
|
|
443
|
+
backend = await self._get_backend()
|
|
444
|
+
# Create tenant-prefixed key if tenant_id provided
|
|
445
|
+
if tenant_id:
|
|
446
|
+
key = f"{tenant_id}:{alias.lower()}"
|
|
447
|
+
else:
|
|
448
|
+
key = alias.lower()
|
|
449
|
+
return await backend.get(key)
|
|
450
|
+
|
|
451
|
+
async def add_alias(
|
|
452
|
+
self,
|
|
453
|
+
alias: str,
|
|
454
|
+
entity_id: str,
|
|
455
|
+
match_type: MatchType = MatchType.ALIAS,
|
|
456
|
+
original_name: Optional[str] = None,
|
|
457
|
+
tenant_id: Optional[str] = None,
|
|
458
|
+
) -> None:
|
|
459
|
+
"""
|
|
460
|
+
Add an alias for an entity.
|
|
461
|
+
|
|
462
|
+
**Tenant Isolation**: When tenant_id is provided, the alias is stored with
|
|
463
|
+
a tenant-prefixed key to ensure isolation.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
alias: The alias string
|
|
467
|
+
entity_id: ID of the entity this alias refers to
|
|
468
|
+
match_type: Type of match (exact, alias, abbreviation, normalized)
|
|
469
|
+
original_name: Original form before normalization
|
|
470
|
+
tenant_id: Optional tenant ID for multi-tenant isolation
|
|
471
|
+
"""
|
|
472
|
+
backend = await self._get_backend()
|
|
473
|
+
entry = AliasEntry(
|
|
474
|
+
entity_id=entity_id,
|
|
475
|
+
match_type=match_type,
|
|
476
|
+
original_name=original_name,
|
|
477
|
+
tenant_id=tenant_id
|
|
478
|
+
)
|
|
479
|
+
await backend.set(alias, entry)
|
|
480
|
+
|
|
481
|
+
async def remove_alias(
|
|
482
|
+
self, alias: str, tenant_id: Optional[str] = None
|
|
483
|
+
) -> bool:
|
|
484
|
+
"""
|
|
485
|
+
Remove an alias.
|
|
486
|
+
|
|
487
|
+
**Tenant Isolation**: When tenant_id is provided, removes the alias from
|
|
488
|
+
the tenant-specific namespace.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
alias: The alias to remove
|
|
492
|
+
tenant_id: Optional tenant ID for multi-tenant isolation
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
True if alias existed and was removed
|
|
496
|
+
"""
|
|
497
|
+
backend = await self._get_backend()
|
|
498
|
+
# Create tenant-prefixed key if tenant_id provided
|
|
499
|
+
if tenant_id:
|
|
500
|
+
key = f"{tenant_id}:{alias.lower()}"
|
|
501
|
+
else:
|
|
502
|
+
key = alias.lower()
|
|
503
|
+
return await backend.delete(key)
|
|
504
|
+
|
|
505
|
+
async def get_entity_aliases(self, entity_id: str) -> List[str]:
|
|
506
|
+
"""
|
|
507
|
+
Get all aliases for an entity.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
entity_id: The entity ID
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
List of aliases for this entity
|
|
514
|
+
"""
|
|
515
|
+
backend = await self._get_backend()
|
|
516
|
+
return await backend.get_by_entity_id(entity_id)
|
|
517
|
+
|
|
518
|
+
async def remove_entity_aliases(self, entity_id: str) -> int:
|
|
519
|
+
"""
|
|
520
|
+
Remove all aliases for an entity.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
entity_id: The entity ID
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Number of aliases removed
|
|
527
|
+
"""
|
|
528
|
+
backend = await self._get_backend()
|
|
529
|
+
aliases = await backend.get_by_entity_id(entity_id)
|
|
530
|
+
for alias in aliases:
|
|
531
|
+
await backend.delete(alias)
|
|
532
|
+
return len(aliases)
|
|
533
|
+
|
|
534
|
+
@asynccontextmanager
|
|
535
|
+
async def transaction(self) -> AsyncIterator[TransactionContext]:
|
|
536
|
+
"""
|
|
537
|
+
Start a transaction for atomic operations.
|
|
538
|
+
|
|
539
|
+
Use for merge operations that need to delete old aliases
|
|
540
|
+
and insert new aliases atomically.
|
|
541
|
+
|
|
542
|
+
Example:
|
|
543
|
+
```python
|
|
544
|
+
async with index.transaction() as tx:
|
|
545
|
+
await tx.delete("old_alias_1")
|
|
546
|
+
await tx.delete("old_alias_2")
|
|
547
|
+
await tx.set("new_alias", AliasEntry(...))
|
|
548
|
+
# All operations committed atomically
|
|
549
|
+
```
|
|
550
|
+
"""
|
|
551
|
+
backend = await self._get_backend()
|
|
552
|
+
async with backend.transaction() as ctx:
|
|
553
|
+
yield ctx
|
|
554
|
+
|
|
555
|
+
async def batch_load(
|
|
556
|
+
self,
|
|
557
|
+
entries: List[tuple], # List of (alias, entity_id, match_type)
|
|
558
|
+
) -> int:
|
|
559
|
+
"""
|
|
560
|
+
Batch load aliases for initial index building.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
entries: List of (alias, entity_id, match_type) tuples
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
Number of entries loaded
|
|
567
|
+
"""
|
|
568
|
+
backend = await self._get_backend()
|
|
569
|
+
count = 0
|
|
570
|
+
for alias, entity_id, match_type in entries:
|
|
571
|
+
entry = AliasEntry(entity_id=entity_id, match_type=match_type)
|
|
572
|
+
await backend.set(alias, entry)
|
|
573
|
+
count += 1
|
|
574
|
+
return count
|
|
575
|
+
|
|
576
|
+
async def clear(self) -> None:
|
|
577
|
+
"""Clear all entries from the index"""
|
|
578
|
+
backend = await self._get_backend()
|
|
579
|
+
await backend.clear()
|
|
580
|
+
|
|
581
|
+
async def size(self) -> int:
|
|
582
|
+
"""Get number of entries in the index"""
|
|
583
|
+
backend = await self._get_backend()
|
|
584
|
+
return await backend.size()
|
|
585
|
+
|
|
586
|
+
async def should_use_redis(self, entity_count: int) -> bool:
|
|
587
|
+
"""
|
|
588
|
+
Check if Redis backend should be used based on entity count.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
entity_count: Number of entities in the graph
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
True if Redis is recommended
|
|
595
|
+
"""
|
|
596
|
+
return entity_count >= self._auto_redis_threshold
|
|
597
|
+
|