irides-core 0.1.0__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.
- core/__init__.py +2 -0
- core/basic_test.py +78 -0
- core/check_mysql_connection.py +23 -0
- core/db_connector/__init__.py +12 -0
- core/db_connector/ai_service.py +133 -0
- core/db_connector/cache_manager.py +69 -0
- core/db_connector/config_service.py +116 -0
- core/db_connector/configurations.py +383 -0
- core/db_connector/connectors/__init__.py +19 -0
- core/db_connector/connectors/athena.py +204 -0
- core/db_connector/connectors/duckdb.py +225 -0
- core/db_connector/connectors/dynamodb.py +233 -0
- core/db_connector/connectors/mongodb.py +337 -0
- core/db_connector/connectors/mysql.py +264 -0
- core/db_connector/connectors/postgres.py +341 -0
- core/db_connector/connectors/sqlite.py +178 -0
- core/db_connector/connectors/trino.py +247 -0
- core/db_connector/exporting/__init__.py +4 -0
- core/db_connector/exporting/artifact_store.py +79 -0
- core/db_connector/exporting/markdown.py +94 -0
- core/db_connector/exporting/models.py +20 -0
- core/db_connector/exporting/okf.py +41 -0
- core/db_connector/exporting/preformatters.py +60 -0
- core/db_connector/interface.py +78 -0
- core/db_connector/job_store.py +315 -0
- core/db_connector/manager.py +75 -0
- core/db_connector/models/__init__.py +7 -0
- core/db_connector/models/column.py +10 -0
- core/db_connector/models/instance.py +7 -0
- core/db_connector/models/scan_job.py +37 -0
- core/db_connector/models/schema.py +7 -0
- core/db_connector/models/table.py +8 -0
- core/db_connector/models/table_details.py +51 -0
- core/db_connector/sql_utils.py +28 -0
- core/db_connector/storage.py +325 -0
- core/setup.py +15 -0
- irides_core-0.1.0.dist-info/METADATA +254 -0
- irides_core-0.1.0.dist-info/RECORD +40 -0
- irides_core-0.1.0.dist-info/WHEEL +5 -0
- irides_core-0.1.0.dist-info/top_level.txt +1 -0
core/__init__.py
ADDED
core/basic_test.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from core.db_connector.connectors.mysql import MySQLConnector
|
|
4
|
+
from core.db_connector.cache_manager import CacheManager
|
|
5
|
+
from core.db_connector.models import TableDescription
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
# Configuration for MySQL Test (ADJUST THESE VALUES)
|
|
9
|
+
MYSQL_CONFIG = {
|
|
10
|
+
"host": "<your_mysql_host>", # e.g., "localhost" or "mysql_test_container"
|
|
11
|
+
"user": "<your_mysql_user>", # e.g., "root"
|
|
12
|
+
"password": "<your_mysql_password>", # e.g., "password"
|
|
13
|
+
"port": 3306,
|
|
14
|
+
# "database": "test_db" # Optional, for specific database operations
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def run_basic_mysql_test():
|
|
18
|
+
logger.info("--- Running Basic MySQL Test ---")
|
|
19
|
+
connector = None
|
|
20
|
+
try:
|
|
21
|
+
cache_manager = CacheManager()
|
|
22
|
+
connector = MySQLConnector(connection_params=MYSQL_CONFIG, cache_manager=cache_manager)
|
|
23
|
+
logger.info("MySQL connection successful!")
|
|
24
|
+
|
|
25
|
+
logger.info("\n--- Listing Instances ---")
|
|
26
|
+
instances = connector.list_instances()
|
|
27
|
+
for instance in instances:
|
|
28
|
+
logger.info(f"Instance: {instance.name} (Version: {instance.version})")
|
|
29
|
+
|
|
30
|
+
logger.info(f"\n--- Listing Schemas for Instance: {instance.name} ---")
|
|
31
|
+
schemas = connector.list_schemas(instance_name=instance.name)
|
|
32
|
+
for schema in schemas:
|
|
33
|
+
logger.info(f" Schema: {schema.name}")
|
|
34
|
+
|
|
35
|
+
logger.info(f"\n --- Listing Tables for Schema: {schema.name} ---")
|
|
36
|
+
tables = connector.list_tables(instance_name=instance.name, schema_name=schema.name)
|
|
37
|
+
if not tables:
|
|
38
|
+
logger.info(f" No tables found in schema: {schema.name}")
|
|
39
|
+
for table in tables:
|
|
40
|
+
logger.info(f" Table: {table.name}")
|
|
41
|
+
|
|
42
|
+
logger.info(f"\n --- Describing Table: {table.name} in Schema: {schema.name} ---")
|
|
43
|
+
table_desc: TableDescription = connector.describe_table(instance_name=instance.name, schema_name=schema.name, table_name=table.name)
|
|
44
|
+
|
|
45
|
+
logger.info(f" Columns:")
|
|
46
|
+
if not table_desc.columns:
|
|
47
|
+
logger.info(f" No columns found for table: {table.name}")
|
|
48
|
+
for col in table_desc.columns:
|
|
49
|
+
logger.info(f" - Name: {col.name}, Type: {col.data_type}, Nullable: {col.is_nullable}, Default: {col.default_value}")
|
|
50
|
+
|
|
51
|
+
if table_desc.primary_key:
|
|
52
|
+
logger.info(f" Primary Key: {', '.join(table_desc.primary_key.column_names)}")
|
|
53
|
+
|
|
54
|
+
if table_desc.foreign_keys:
|
|
55
|
+
logger.info(f" Foreign Keys:")
|
|
56
|
+
for fk in table_desc.foreign_keys:
|
|
57
|
+
logger.info(f" - Column: {fk.column_name} -> {fk.referenced_table}.{fk.referenced_column} (Constraint: {fk.constraint_name})")
|
|
58
|
+
|
|
59
|
+
if table_desc.indexes:
|
|
60
|
+
logger.info(f" Indexes:")
|
|
61
|
+
for idx in table_desc.indexes:
|
|
62
|
+
logger.info(f" - Name: {idx.name}, Columns: {', '.join(idx.column_names)}, Unique: {idx.is_unique}, Primary: {idx.is_primary}, Type: {idx.type}")
|
|
63
|
+
|
|
64
|
+
logger.info("-" * 40) # Separator for readability
|
|
65
|
+
|
|
66
|
+
except ConnectionError as e:
|
|
67
|
+
logger.error(f"MySQL connection failed: {e}")
|
|
68
|
+
logger.exception("Connection Error Traceback:")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"An unexpected error occurred: {e}")
|
|
72
|
+
logger.exception("Unexpected Error Traceback:")
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
finally:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
run_basic_mysql_test()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from core.db_connector.connectors.mysql import MySQLConnector
|
|
3
|
+
from core.db_connector.cache_manager import CacheManager
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
MYSQL_TEST_CONFIG = {
|
|
7
|
+
"host": "host.docker.internal",
|
|
8
|
+
"user": "root",
|
|
9
|
+
"password": "",
|
|
10
|
+
"port": 3306,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
logger.info("Attempting MySQL connection check...")
|
|
15
|
+
temp_conn = MySQLConnector(
|
|
16
|
+
connection_params=MYSQL_TEST_CONFIG,
|
|
17
|
+
cache_manager=CacheManager(),
|
|
18
|
+
)
|
|
19
|
+
logger.info("MySQL connection successful!")
|
|
20
|
+
except Exception as e:
|
|
21
|
+
logger.error(f"MySQL connection failed: {e}")
|
|
22
|
+
logger.exception("Connection Error Traceback:")
|
|
23
|
+
sys.exit(1) # Exit with error code if connection fails
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .manager import ConnectorManager
|
|
2
|
+
from .storage import BaseMetadataStore, FileMetadataStore, get_metadata_store
|
|
3
|
+
from .ai_service import AIDocumentationService
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ConnectorManager",
|
|
7
|
+
"BaseMetadataStore",
|
|
8
|
+
"FileMetadataStore",
|
|
9
|
+
"get_metadata_store",
|
|
10
|
+
"AIDocumentationService",
|
|
11
|
+
]
|
|
12
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AIDocumentationService:
|
|
10
|
+
"""Service to generate basic AI documentation for database table schemas using LiteLLM."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, model_name: Optional[str] = None):
|
|
13
|
+
self.model_name = model_name or os.getenv("LITELLM_MODEL") or "gpt-4o-mini"
|
|
14
|
+
self.timeout = int(os.getenv("LITELLM_TIMEOUT_SECONDS", "120"))
|
|
15
|
+
self.max_retries = int(os.getenv("LITELLM_MAX_RETRIES", "2"))
|
|
16
|
+
self.last_error: Optional[str] = None
|
|
17
|
+
|
|
18
|
+
def generate_table_documentation(
|
|
19
|
+
self, table_description: Dict[str, Any]
|
|
20
|
+
) -> Optional[Dict[str, Any]]:
|
|
21
|
+
"""Generates AI documentation for a table description payload using LiteLLM.
|
|
22
|
+
|
|
23
|
+
This method is non-blocking: if LiteLLM is not installed, if API keys are missing,
|
|
24
|
+
or if any API/network error occurs, it catches the exception, logs a warning,
|
|
25
|
+
and returns None without throwing an exception.
|
|
26
|
+
"""
|
|
27
|
+
table_name = table_description.get("table_name", "unknown")
|
|
28
|
+
schema_name = table_description.get("schema_name", "unknown")
|
|
29
|
+
instance_name = table_description.get("instance_name", "unknown")
|
|
30
|
+
columns = table_description.get("columns", [])
|
|
31
|
+
self.last_error = None
|
|
32
|
+
|
|
33
|
+
prompt = (
|
|
34
|
+
f"Analyse the following database table schema and generate a concise business documentation.\n"
|
|
35
|
+
f"Instance Name: {instance_name}\n"
|
|
36
|
+
f"Schema Name: {schema_name}\n"
|
|
37
|
+
f"Table Name: {table_name}\n"
|
|
38
|
+
f"Columns: {json.dumps(columns, default=str)}\n"
|
|
39
|
+
f"Primary Key: {table_description.get('primary_key', [])}\n"
|
|
40
|
+
f"Foreign Keys: {json.dumps(table_description.get('foreign_keys', []), default=str)}\n\n"
|
|
41
|
+
f"Return a valid JSON object with the following keys:\n"
|
|
42
|
+
f"- 'summary': A high-level sentence describing the purpose of the table.\n"
|
|
43
|
+
f"- 'column_descriptions': A map of column_name -> brief functional description.\n"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import litellm
|
|
48
|
+
except ImportError:
|
|
49
|
+
self.last_error = "litellm package is not installed"
|
|
50
|
+
logger.warning(f"AIDocumentationService: {self.last_error}. Skipping AI doc generation.")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
logger.info(
|
|
55
|
+
f"AIDocumentationService: Generating AI docs for table '{table_name}' using model '{self.model_name}'"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
kwargs = {}
|
|
59
|
+
if os.getenv("LITELLM_API_KEY"):
|
|
60
|
+
kwargs["api_key"] = os.getenv("LITELLM_API_KEY")
|
|
61
|
+
if os.getenv("LITELLM_API_BASE"):
|
|
62
|
+
kwargs["api_base"] = os.getenv("LITELLM_API_BASE")
|
|
63
|
+
|
|
64
|
+
messages = [
|
|
65
|
+
{
|
|
66
|
+
"role": "system",
|
|
67
|
+
"content": "You are an expert Data Architect. Produce clean, structured schema documentation in valid JSON format.",
|
|
68
|
+
},
|
|
69
|
+
{"role": "user", "content": prompt},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
last_attempt_error: Optional[Exception] = None
|
|
73
|
+
response = None
|
|
74
|
+
for attempt in range(self.max_retries):
|
|
75
|
+
try:
|
|
76
|
+
response = litellm.completion(
|
|
77
|
+
model=self.model_name,
|
|
78
|
+
messages=messages,
|
|
79
|
+
response_format={"type": "json_object"},
|
|
80
|
+
timeout=self.timeout,
|
|
81
|
+
**kwargs,
|
|
82
|
+
)
|
|
83
|
+
break
|
|
84
|
+
except Exception as attempt_exc:
|
|
85
|
+
last_attempt_error = attempt_exc
|
|
86
|
+
logger.warning(
|
|
87
|
+
f"AIDocumentationService: attempt {attempt + 1}/{self.max_retries} failed for '{table_name}': {attempt_exc}"
|
|
88
|
+
)
|
|
89
|
+
if attempt < self.max_retries - 1:
|
|
90
|
+
time.sleep(2)
|
|
91
|
+
|
|
92
|
+
if response is None:
|
|
93
|
+
raise last_attempt_error
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
content = response.choices[0].message.content
|
|
97
|
+
if not content:
|
|
98
|
+
self.last_error = f"Empty response content from model '{self.model_name}'"
|
|
99
|
+
logger.warning(f"AIDocumentationService: {self.last_error} for table '{table_name}'")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# Strip markdown code fences if present (e.g. ```json ... ```)
|
|
103
|
+
stripped = content.strip()
|
|
104
|
+
if stripped.startswith("```"):
|
|
105
|
+
stripped = stripped.split("\n", 1)[1] if "\n" in stripped else stripped[3:]
|
|
106
|
+
if stripped.endswith("```"):
|
|
107
|
+
stripped = stripped[:-3].strip()
|
|
108
|
+
else:
|
|
109
|
+
stripped = stripped
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
parsed_doc = json.loads(stripped)
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
self.last_error = f"Non-JSON response from model '{self.model_name}': {content!r:.200}"
|
|
115
|
+
logger.warning(f"AIDocumentationService: {self.last_error} for table '{table_name}'")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
logger.info(
|
|
119
|
+
f"AIDocumentationService: AI docs generated for table '{table_name}' using model '{self.model_name}'"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"summary": parsed_doc.get("summary", ""),
|
|
124
|
+
"column_descriptions": parsed_doc.get("column_descriptions", {}),
|
|
125
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
126
|
+
"model": self.model_name,
|
|
127
|
+
}
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self.last_error = str(e)
|
|
130
|
+
logger.warning(
|
|
131
|
+
f"AIDocumentationService: Failed or skipped AI doc generation for table '{table_name}': {e}"
|
|
132
|
+
)
|
|
133
|
+
return None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import redis
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
class CacheManager:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
host: str = 'localhost',
|
|
11
|
+
port: int = 6379,
|
|
12
|
+
db: int = 0,
|
|
13
|
+
ttl_seconds: int = 86400,
|
|
14
|
+
project_prefix: Optional[str] = None,
|
|
15
|
+
socket_connect_timeout: float = 2.0,
|
|
16
|
+
socket_timeout: float = 2.0,
|
|
17
|
+
):
|
|
18
|
+
self.redis_client = None
|
|
19
|
+
self.ttl_seconds = ttl_seconds # Default 1 day
|
|
20
|
+
self.project_prefix = project_prefix if project_prefix is not None else os.getenv("CACHE_KEY_PREFIX", "multi-db-connector")
|
|
21
|
+
try:
|
|
22
|
+
self.redis_client = redis.Redis(
|
|
23
|
+
host=host,
|
|
24
|
+
port=port,
|
|
25
|
+
db=db,
|
|
26
|
+
decode_responses=True,
|
|
27
|
+
socket_connect_timeout=socket_connect_timeout,
|
|
28
|
+
socket_timeout=socket_timeout,
|
|
29
|
+
health_check_interval=30,
|
|
30
|
+
)
|
|
31
|
+
self.redis_client.ping()
|
|
32
|
+
logger.info(f"Successfully connected to Redis at {host}:{port}/{db}")
|
|
33
|
+
except redis.exceptions.RedisError as e:
|
|
34
|
+
logger.error(f"Could not connect to Redis cache at {host}:{port}/{db}: {e}")
|
|
35
|
+
self.redis_client = None # Ensure client is None if connection fails
|
|
36
|
+
|
|
37
|
+
def _serialize(self, data: Any) -> str:
|
|
38
|
+
"""Serializes data to JSON string."""
|
|
39
|
+
return json.dumps(data)
|
|
40
|
+
|
|
41
|
+
def _deserialize(self, data_str: str) -> Any:
|
|
42
|
+
"""Deserializes JSON string to data."""
|
|
43
|
+
return json.loads(data_str)
|
|
44
|
+
|
|
45
|
+
def get_cached_data(self, key: str, no_cache: bool = False) -> Optional[Any]:
|
|
46
|
+
prefixed_key = f"{self.project_prefix}:{key}" # Add prefix
|
|
47
|
+
if not self.redis_client or no_cache:
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
cached_data = self.redis_client.get(prefixed_key)
|
|
51
|
+
if cached_data:
|
|
52
|
+
logger.debug(f"Cache hit for key: {prefixed_key}")
|
|
53
|
+
return self._deserialize(cached_data)
|
|
54
|
+
logger.debug(f"Cache miss for key: {prefixed_key}")
|
|
55
|
+
return None
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Error retrieving data from Redis for key {prefixed_key}: {e}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def set_cached_data(self, key: str, data: Any, ttl: Optional[int] = None):
|
|
61
|
+
prefixed_key = f"{self.project_prefix}:{key}" # Add prefix
|
|
62
|
+
if not self.redis_client:
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
ttl_to_use = ttl if ttl is not None else self.ttl_seconds
|
|
66
|
+
self.redis_client.setex(prefixed_key, ttl_to_use, self._serialize(data))
|
|
67
|
+
logger.debug(f"Data set in cache for key: {prefixed_key} with TTL: {ttl_to_use}s")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"Error setting data in Redis for key {prefixed_key}: {e}")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
|
2
|
+
from loguru import logger
|
|
3
|
+
|
|
4
|
+
from core.db_connector.manager import ConnectorManager
|
|
5
|
+
from core.db_connector.models import Instance
|
|
6
|
+
from core.db_connector.configurations import get_db_configurations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigService:
|
|
10
|
+
def __init__(self, connector_manager: ConnectorManager, config_file: Optional[str] = None):
|
|
11
|
+
self.connector_manager = connector_manager
|
|
12
|
+
self.db_configurations: Dict[str, Dict[str, Any]] = get_db_configurations(config_file=config_file)
|
|
13
|
+
logger.info(f"ConfigService: Initialized with {len(self.db_configurations)} database configurations.")
|
|
14
|
+
|
|
15
|
+
def get_available_configurations(self) -> List[str]:
|
|
16
|
+
logger.info("ConfigService: Fetching available configurations.")
|
|
17
|
+
return list(self.db_configurations.keys())
|
|
18
|
+
|
|
19
|
+
def _get_connector_details(self, config_name: str) -> Dict[str, Any]:
|
|
20
|
+
config = self.db_configurations.get(config_name)
|
|
21
|
+
if not config:
|
|
22
|
+
logger.error(f"ConfigService: Configuration name '{config_name}' not found.")
|
|
23
|
+
raise ValueError(f"Configuration name '{config_name}' not found.")
|
|
24
|
+
|
|
25
|
+
connector_type = config.get("connector_type")
|
|
26
|
+
connection_params = config.get("connection_params")
|
|
27
|
+
|
|
28
|
+
if not connector_type or not connection_params:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"ConfigService: Configuration '{config_name}' is missing "
|
|
31
|
+
"'connector_type' or 'connection_params'."
|
|
32
|
+
)
|
|
33
|
+
return {"connector_type": connector_type, "connection_params": connection_params}
|
|
34
|
+
|
|
35
|
+
def _get_hosts(self, config_name: str) -> List[str]:
|
|
36
|
+
"""Return explicitly configured connection hosts, when present.
|
|
37
|
+
|
|
38
|
+
Configurations may use either a multi-host ``hosts`` collection or a
|
|
39
|
+
flat ``host`` connection parameter. Connectors such as Athena and
|
|
40
|
+
DynamoDB discover instances remotely and expose neither.
|
|
41
|
+
"""
|
|
42
|
+
details = self._get_connector_details(config_name)
|
|
43
|
+
connection_params = details["connection_params"]
|
|
44
|
+
hosts = [item["host"] for item in connection_params.get("hosts", [])]
|
|
45
|
+
if hosts:
|
|
46
|
+
return hosts
|
|
47
|
+
return [connection_params["host"]] if connection_params.get("host") else []
|
|
48
|
+
|
|
49
|
+
def configuration_matches_instance(
|
|
50
|
+
self, config_name: str, instance_name: str, no_cache: bool = False
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Return whether an instance belongs to a database configuration."""
|
|
53
|
+
configured_hosts = self._get_hosts(config_name)
|
|
54
|
+
if configured_hosts:
|
|
55
|
+
return instance_name in configured_hosts
|
|
56
|
+
return instance_name in {
|
|
57
|
+
instance.name for instance in self.list_instances(config_name, no_cache=no_cache)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def resolve_configurations_for_instance(
|
|
61
|
+
self, instance_name: str, no_cache: bool = False
|
|
62
|
+
) -> List[str]:
|
|
63
|
+
return [
|
|
64
|
+
config_name
|
|
65
|
+
for config_name in self.get_available_configurations()
|
|
66
|
+
if self.configuration_matches_instance(config_name, instance_name, no_cache)
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def _get_connector_for_host(self, config_name: str, host: str):
|
|
70
|
+
details = self._get_connector_details(config_name)
|
|
71
|
+
host_params = next(
|
|
72
|
+
(h for h in details["connection_params"].get("hosts", []) if h["host"] == host),
|
|
73
|
+
None,
|
|
74
|
+
)
|
|
75
|
+
if host_params:
|
|
76
|
+
return self.connector_manager.get_connector(details["connector_type"], host_params)
|
|
77
|
+
|
|
78
|
+
# Flat connection parameters identify one connection, while ``host`` is
|
|
79
|
+
# an instance/catalog/region discovered through that connection.
|
|
80
|
+
if not details["connection_params"].get("hosts"):
|
|
81
|
+
return self.connector_manager.get_connector(
|
|
82
|
+
details["connector_type"], details["connection_params"]
|
|
83
|
+
)
|
|
84
|
+
raise ValueError(f"Host '{host}' not found in config '{config_name}'")
|
|
85
|
+
|
|
86
|
+
def list_instances(self, config_name: str, no_cache: bool = False) -> List[Instance]:
|
|
87
|
+
"""List instances for both multi-host and flat connector configurations."""
|
|
88
|
+
configured_hosts = self._get_hosts(config_name)
|
|
89
|
+
if configured_hosts:
|
|
90
|
+
instances: List[Instance] = []
|
|
91
|
+
for host in configured_hosts:
|
|
92
|
+
connector = self._get_connector_for_host(config_name, host)
|
|
93
|
+
instances.extend(connector.list_instances(no_cache=no_cache))
|
|
94
|
+
return instances
|
|
95
|
+
|
|
96
|
+
details = self._get_connector_details(config_name)
|
|
97
|
+
connector = self.connector_manager.get_connector(
|
|
98
|
+
details["connector_type"], details["connection_params"]
|
|
99
|
+
)
|
|
100
|
+
return connector.list_instances(no_cache=no_cache)
|
|
101
|
+
|
|
102
|
+
def resolve_instance_names(
|
|
103
|
+
self, config_name: str, instance_name: Optional[str] = None, no_cache: bool = False
|
|
104
|
+
) -> List[str]:
|
|
105
|
+
if instance_name:
|
|
106
|
+
return [instance_name]
|
|
107
|
+
return [instance.name for instance in self.list_instances(config_name, no_cache)]
|
|
108
|
+
|
|
109
|
+
def test_connection(self, config_name: str):
|
|
110
|
+
logger.info(f"ConfigService: Attempting to test connection for config: {config_name}")
|
|
111
|
+
for instance in self.list_instances(config_name, no_cache=True):
|
|
112
|
+
logger.info(
|
|
113
|
+
f"ConfigService: Successfully tested connection to {instance.name} "
|
|
114
|
+
f"for config: {config_name}"
|
|
115
|
+
)
|
|
116
|
+
return {"message": f"Successfully connected to all hosts in {config_name}."}
|