couchbase-mcp-server 0.4.0rc1__py3-none-any.whl → 0.5.1__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.
- certs/__init__.py +0 -0
- certs/capella_root_ca.pem +19 -0
- {couchbase_mcp_server-0.4.0rc1.dist-info → couchbase_mcp_server-0.5.1.dist-info}/METADATA +76 -39
- couchbase_mcp_server-0.5.1.dist-info/RECORD +19 -0
- {couchbase_mcp_server-0.4.0rc1.dist-info → couchbase_mcp_server-0.5.1.dist-info}/WHEEL +1 -2
- mcp_server.py +22 -6
- tools/__init__.py +19 -0
- tools/index.py +172 -0
- tools/kv.py +20 -6
- tools/query.py +27 -6
- tools/server.py +105 -16
- utils/__init__.py +9 -6
- utils/config.py +0 -25
- utils/connection.py +29 -4
- utils/context.py +21 -37
- utils/index_utils.py +308 -0
- couchbase_mcp_server-0.4.0rc1.dist-info/RECORD +0 -16
- couchbase_mcp_server-0.4.0rc1.dist-info/top_level.txt +0 -3
- {couchbase_mcp_server-0.4.0rc1.dist-info → couchbase_mcp_server-0.5.1.dist-info}/entry_points.txt +0 -0
- {couchbase_mcp_server-0.4.0rc1.dist-info → couchbase_mcp_server-0.5.1.dist-info}/licenses/LICENSE +0 -0
tools/query.py
CHANGED
|
@@ -10,22 +10,23 @@ from typing import Any
|
|
|
10
10
|
from lark_sqlpp import modifies_data, modifies_structure, parse_sqlpp
|
|
11
11
|
from mcp.server.fastmcp import Context
|
|
12
12
|
|
|
13
|
+
from utils.connection import connect_to_bucket
|
|
13
14
|
from utils.constants import MCP_SERVER_NAME
|
|
14
|
-
from utils.context import
|
|
15
|
+
from utils.context import get_cluster_connection
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.query")
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def get_schema_for_collection(
|
|
20
|
-
ctx: Context, scope_name: str, collection_name: str
|
|
21
|
+
ctx: Context, bucket_name: str, scope_name: str, collection_name: str
|
|
21
22
|
) -> dict[str, Any]:
|
|
22
23
|
"""Get the schema for a collection in the specified scope.
|
|
23
24
|
Returns a dictionary with the collection name and the schema returned by running INFER query on the Couchbase collection.
|
|
24
25
|
"""
|
|
25
26
|
schema = {"collection_name": collection_name, "schema": []}
|
|
26
27
|
try:
|
|
27
|
-
query = f"INFER {collection_name}"
|
|
28
|
-
result = run_sql_plus_plus_query(ctx, scope_name, query)
|
|
28
|
+
query = f"INFER `{collection_name}`"
|
|
29
|
+
result = run_sql_plus_plus_query(ctx, bucket_name, scope_name, query)
|
|
29
30
|
# Result is a list of list of schemas. We convert it to a list of schemas.
|
|
30
31
|
if result:
|
|
31
32
|
schema["schema"] = result[0]
|
|
@@ -36,10 +37,13 @@ def get_schema_for_collection(
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def run_sql_plus_plus_query(
|
|
39
|
-
ctx: Context, scope_name: str, query: str
|
|
40
|
+
ctx: Context, bucket_name: str, scope_name: str, query: str
|
|
40
41
|
) -> list[dict[str, Any]]:
|
|
41
42
|
"""Run a SQL++ query on a scope and return the results as a list of JSON objects."""
|
|
42
|
-
|
|
43
|
+
cluster = get_cluster_connection(ctx)
|
|
44
|
+
|
|
45
|
+
bucket = connect_to_bucket(cluster, bucket_name)
|
|
46
|
+
|
|
43
47
|
app_context = ctx.request_context.lifespan_context
|
|
44
48
|
read_only_query_mode = app_context.read_only_query_mode
|
|
45
49
|
logger.info(f"Running SQL++ queries in read-only mode: {read_only_query_mode}")
|
|
@@ -75,3 +79,20 @@ def run_sql_plus_plus_query(
|
|
|
75
79
|
except Exception as e:
|
|
76
80
|
logger.error(f"Error running query: {e!s}", exc_info=True)
|
|
77
81
|
raise
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Don't expose this function to the MCP server until we have a use case
|
|
85
|
+
def run_cluster_query(ctx: Context, query: str, **kwargs: Any) -> list[dict[str, Any]]:
|
|
86
|
+
"""Run a query on the cluster object and return the results as a list of JSON objects."""
|
|
87
|
+
|
|
88
|
+
cluster = get_cluster_connection(ctx)
|
|
89
|
+
results = []
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
result = cluster.query(query, **kwargs)
|
|
93
|
+
for row in result:
|
|
94
|
+
results.append(row)
|
|
95
|
+
return results
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Error running query: {e}")
|
|
98
|
+
raise
|
tools/server.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tools for server operations.
|
|
3
3
|
|
|
4
|
-
This module contains tools for getting the server status, testing the connection, and getting the scopes and collections in the bucket.
|
|
4
|
+
This module contains tools for getting the server status, testing the connection, and getting the buckets in the cluster, the scopes and collections in the bucket.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import json
|
|
7
8
|
import logging
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
from mcp.server.fastmcp import Context
|
|
11
12
|
|
|
13
|
+
from tools.query import run_cluster_query
|
|
12
14
|
from utils.config import get_settings
|
|
15
|
+
from utils.connection import connect_to_bucket
|
|
13
16
|
from utils.constants import MCP_SERVER_NAME
|
|
14
|
-
from utils.context import
|
|
17
|
+
from utils.context import get_cluster_connection
|
|
15
18
|
|
|
16
19
|
logger = logging.getLogger(f"{MCP_SERVER_NAME}.tools.server")
|
|
17
20
|
|
|
@@ -26,15 +29,16 @@ def get_server_configuration_status(ctx: Context) -> dict[str, Any]:
|
|
|
26
29
|
configuration = {
|
|
27
30
|
"connection_string": settings.get("connection_string", "Not set"),
|
|
28
31
|
"username": settings.get("username", "Not set"),
|
|
29
|
-
"bucket_name": settings.get("bucket_name", "Not set"),
|
|
30
32
|
"read_only_query_mode": settings.get("read_only_query_mode", True),
|
|
31
33
|
"password_configured": bool(settings.get("password")),
|
|
34
|
+
"ca_cert_path_configured": bool(settings.get("ca_cert_path")),
|
|
35
|
+
"client_cert_path_configured": bool(settings.get("client_cert_path")),
|
|
36
|
+
"client_key_path_configured": bool(settings.get("client_key_path")),
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
app_context = ctx.request_context.lifespan_context
|
|
35
40
|
connection_status = {
|
|
36
41
|
"cluster_connected": app_context.cluster is not None,
|
|
37
|
-
"bucket_connected": app_context.bucket is not None,
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
return {
|
|
@@ -45,39 +49,46 @@ def get_server_configuration_status(ctx: Context) -> dict[str, Any]:
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
def test_cluster_connection(
|
|
49
|
-
|
|
52
|
+
def test_cluster_connection(
|
|
53
|
+
ctx: Context, bucket_name: str | None = None
|
|
54
|
+
) -> dict[str, Any]:
|
|
55
|
+
"""Test the connection to Couchbase cluster and optionally to a bucket.
|
|
50
56
|
This tool verifies the connection to the Couchbase cluster and bucket by establishing the connection if it is not already established.
|
|
57
|
+
If bucket name is not provided, it will not try to connect to the bucket specified in the MCP server settings.
|
|
51
58
|
Returns connection status and basic cluster information.
|
|
52
59
|
"""
|
|
53
60
|
try:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
cluster = get_cluster_connection(ctx)
|
|
62
|
+
bucket = None
|
|
63
|
+
if bucket_name:
|
|
64
|
+
bucket = connect_to_bucket(cluster, bucket_name)
|
|
58
65
|
|
|
59
66
|
return {
|
|
60
67
|
"status": "success",
|
|
61
|
-
"cluster_connected":
|
|
62
|
-
"bucket_connected":
|
|
68
|
+
"cluster_connected": cluster.connected,
|
|
69
|
+
"bucket_connected": bucket is not None,
|
|
63
70
|
"bucket_name": bucket_name,
|
|
64
|
-
"message": "Successfully connected to Couchbase cluster
|
|
71
|
+
"message": "Successfully connected to Couchbase cluster",
|
|
65
72
|
}
|
|
66
73
|
except Exception as e:
|
|
67
74
|
return {
|
|
68
75
|
"status": "error",
|
|
69
76
|
"cluster_connected": False,
|
|
70
77
|
"bucket_connected": False,
|
|
78
|
+
"bucket_name": bucket_name,
|
|
71
79
|
"error": str(e),
|
|
72
|
-
"message": "Failed to connect to Couchbase",
|
|
80
|
+
"message": "Failed to connect to Couchbase cluster",
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
|
|
76
|
-
def get_scopes_and_collections_in_bucket(
|
|
84
|
+
def get_scopes_and_collections_in_bucket(
|
|
85
|
+
ctx: Context, bucket_name: str
|
|
86
|
+
) -> dict[str, list[str]]:
|
|
77
87
|
"""Get the names of all scopes and collections in the bucket.
|
|
78
88
|
Returns a dictionary with scope names as keys and lists of collection names as values.
|
|
79
89
|
"""
|
|
80
|
-
|
|
90
|
+
cluster = get_cluster_connection(ctx)
|
|
91
|
+
bucket = connect_to_bucket(cluster, bucket_name)
|
|
81
92
|
try:
|
|
82
93
|
scopes_collections = {}
|
|
83
94
|
collection_manager = bucket.collections()
|
|
@@ -89,3 +100,81 @@ def get_scopes_and_collections_in_bucket(ctx: Context) -> dict[str, list[str]]:
|
|
|
89
100
|
except Exception as e:
|
|
90
101
|
logger.error(f"Error getting scopes and collections: {e}")
|
|
91
102
|
raise
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_buckets_in_cluster(ctx: Context) -> list[str]:
|
|
106
|
+
"""Get the names of all the accessible buckets in the cluster."""
|
|
107
|
+
cluster = get_cluster_connection(ctx)
|
|
108
|
+
bucket_manager = cluster.buckets()
|
|
109
|
+
buckets_with_settings = bucket_manager.get_all_buckets()
|
|
110
|
+
|
|
111
|
+
buckets = []
|
|
112
|
+
for bucket in buckets_with_settings:
|
|
113
|
+
buckets.append(bucket.name)
|
|
114
|
+
|
|
115
|
+
return buckets
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_scopes_in_bucket(ctx: Context, bucket_name: str) -> list[str]:
|
|
119
|
+
"""Get the names of all scopes in the given bucket."""
|
|
120
|
+
cluster = get_cluster_connection(ctx)
|
|
121
|
+
bucket = connect_to_bucket(cluster, bucket_name)
|
|
122
|
+
try:
|
|
123
|
+
scopes = bucket.collections().get_all_scopes()
|
|
124
|
+
return [scope.name for scope in scopes]
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Error getting scopes in the bucket {bucket_name}: {e}")
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_collections_in_scope(
|
|
131
|
+
ctx: Context, bucket_name: str, scope_name: str
|
|
132
|
+
) -> list[str]:
|
|
133
|
+
"""Get the names of all collections in the given scope and bucket."""
|
|
134
|
+
|
|
135
|
+
# Get the collections in the scope using system:all_keyspaces collection
|
|
136
|
+
query = "SELECT DISTINCT(name) as collection_name FROM system:all_keyspaces where `bucket`=$bucket_name and `scope`=$scope_name"
|
|
137
|
+
results = run_cluster_query(
|
|
138
|
+
ctx, query, bucket_name=bucket_name, scope_name=scope_name
|
|
139
|
+
)
|
|
140
|
+
return [result["collection_name"] for result in results]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_cluster_health_and_services(
|
|
144
|
+
ctx: Context, bucket_name: str | None = None
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
"""Get cluster health status and list of all running services.
|
|
147
|
+
|
|
148
|
+
This tool provides health monitoring by:
|
|
149
|
+
- Getting health status of all running services with latency information (via ping)
|
|
150
|
+
- Listing all services running on the cluster with their endpoints
|
|
151
|
+
- Showing connection status and node information for each service
|
|
152
|
+
|
|
153
|
+
If bucket_name is provided, it actively pings services from the perspective of the bucket.
|
|
154
|
+
Otherwise, it uses cluster-level ping to get the health status of the cluster.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
- Cluster health status with service-level connection details and latency measurements
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
cluster = get_cluster_connection(ctx)
|
|
161
|
+
|
|
162
|
+
if bucket_name:
|
|
163
|
+
# Ping services from the perspective of the bucket
|
|
164
|
+
bucket = connect_to_bucket(cluster, bucket_name)
|
|
165
|
+
result = bucket.ping().as_json()
|
|
166
|
+
else:
|
|
167
|
+
# Ping services from the perspective of the cluster
|
|
168
|
+
result = cluster.ping().as_json()
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
"status": "success",
|
|
172
|
+
"data": json.loads(result),
|
|
173
|
+
}
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Error getting cluster health: {e}")
|
|
176
|
+
return {
|
|
177
|
+
"status": "error",
|
|
178
|
+
"error": str(e),
|
|
179
|
+
"message": "Failed to get cluster health and services information",
|
|
180
|
+
}
|
utils/__init__.py
CHANGED
|
@@ -7,8 +7,6 @@ This module contains utility functions for configuration, connection, and contex
|
|
|
7
7
|
# Configuration utilities
|
|
8
8
|
from .config import (
|
|
9
9
|
get_settings,
|
|
10
|
-
validate_connection_config,
|
|
11
|
-
validate_required_param,
|
|
12
10
|
)
|
|
13
11
|
|
|
14
12
|
# Connection utilities
|
|
@@ -33,7 +31,12 @@ from .constants import (
|
|
|
33
31
|
# Context utilities
|
|
34
32
|
from .context import (
|
|
35
33
|
AppContext,
|
|
36
|
-
|
|
34
|
+
get_cluster_connection,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Index utilities
|
|
38
|
+
from .index_utils import (
|
|
39
|
+
fetch_indexes_from_rest_api,
|
|
37
40
|
)
|
|
38
41
|
|
|
39
42
|
# Note: Individual modules create their own hierarchical loggers using:
|
|
@@ -42,14 +45,14 @@ from .context import (
|
|
|
42
45
|
__all__ = [
|
|
43
46
|
# Config
|
|
44
47
|
"get_settings",
|
|
45
|
-
"validate_required_param",
|
|
46
|
-
"validate_connection_config",
|
|
47
48
|
# Connection
|
|
48
49
|
"connect_to_couchbase_cluster",
|
|
49
50
|
"connect_to_bucket",
|
|
50
51
|
# Context
|
|
51
52
|
"AppContext",
|
|
52
|
-
"
|
|
53
|
+
"get_cluster_connection",
|
|
54
|
+
# Index utilities
|
|
55
|
+
"fetch_indexes_from_rest_api",
|
|
53
56
|
# Constants
|
|
54
57
|
"MCP_SERVER_NAME",
|
|
55
58
|
"DEFAULT_READ_ONLY_MODE",
|
utils/config.py
CHANGED
|
@@ -7,32 +7,7 @@ from .constants import MCP_SERVER_NAME
|
|
|
7
7
|
logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.config")
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def validate_required_param(
|
|
11
|
-
ctx: click.Context, param: click.Parameter, value: str | None
|
|
12
|
-
) -> str:
|
|
13
|
-
"""Validate that a required parameter is not empty."""
|
|
14
|
-
if not value or value.strip() == "":
|
|
15
|
-
raise click.BadParameter(f"{param.name} cannot be empty")
|
|
16
|
-
return value
|
|
17
|
-
|
|
18
|
-
|
|
19
10
|
def get_settings() -> dict:
|
|
20
11
|
"""Get settings from Click context."""
|
|
21
12
|
ctx = click.get_current_context()
|
|
22
13
|
return ctx.obj or {}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def validate_connection_config() -> None:
|
|
26
|
-
"""Validate that all required parameters for the MCP server are available when needed."""
|
|
27
|
-
settings = get_settings()
|
|
28
|
-
required_params = ["connection_string", "username", "password", "bucket_name"]
|
|
29
|
-
missing_params = []
|
|
30
|
-
|
|
31
|
-
for param in required_params:
|
|
32
|
-
if not settings.get(param):
|
|
33
|
-
missing_params.append(param)
|
|
34
|
-
|
|
35
|
-
if missing_params:
|
|
36
|
-
error_msg = f"Missing required parameters for the MCP server: {', '.join(missing_params)}"
|
|
37
|
-
logger.error(error_msg)
|
|
38
|
-
raise ValueError(error_msg)
|
utils/connection.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
from datetime import timedelta
|
|
3
4
|
|
|
4
|
-
from couchbase.auth import PasswordAuthenticator
|
|
5
|
+
from couchbase.auth import CertificateAuthenticator, PasswordAuthenticator
|
|
5
6
|
from couchbase.cluster import Bucket, Cluster
|
|
6
7
|
from couchbase.options import ClusterOptions
|
|
7
8
|
|
|
@@ -11,15 +12,40 @@ logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.connection")
|
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def connect_to_couchbase_cluster(
|
|
14
|
-
connection_string: str,
|
|
15
|
+
connection_string: str,
|
|
16
|
+
username: str,
|
|
17
|
+
password: str,
|
|
18
|
+
ca_cert_path: str | None = None,
|
|
19
|
+
client_cert_path: str | None = None,
|
|
20
|
+
client_key_path: str | None = None,
|
|
15
21
|
) -> Cluster:
|
|
16
22
|
"""Connect to Couchbase cluster and return the cluster object if successful.
|
|
23
|
+
The connection can be established using the client certificate and key or the username and password. Optionally, the CA root certificate path can also be provided.
|
|
24
|
+
Either of the path to the client certificate and key or the username and password should be provided.
|
|
25
|
+
If the client certificate and key are provided, the username and password are not used.
|
|
26
|
+
If both the client certificate and key and the username and password are provided, the client certificate is used for authentication.
|
|
17
27
|
If the connection fails, it will raise an exception.
|
|
18
28
|
"""
|
|
19
29
|
|
|
20
30
|
try:
|
|
21
31
|
logger.info("Connecting to Couchbase cluster...")
|
|
22
|
-
|
|
32
|
+
if client_cert_path and client_key_path:
|
|
33
|
+
logger.info("Connecting to Couchbase cluster with client certificate...")
|
|
34
|
+
if not os.path.exists(client_cert_path) or not os.path.exists(
|
|
35
|
+
client_key_path
|
|
36
|
+
):
|
|
37
|
+
raise FileNotFoundError(
|
|
38
|
+
f"Client certificate files not found at {os.path.basename(client_cert_path)} or {os.path.basename(client_key_path)}."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
auth = CertificateAuthenticator(
|
|
42
|
+
cert_path=client_cert_path,
|
|
43
|
+
key_path=client_key_path,
|
|
44
|
+
trust_store_path=ca_cert_path,
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
logger.info("Connecting to Couchbase cluster with password...")
|
|
48
|
+
auth = PasswordAuthenticator(username, password, cert_path=ca_cert_path)
|
|
23
49
|
options = ClusterOptions(auth)
|
|
24
50
|
options.apply_profile("wan_development")
|
|
25
51
|
|
|
@@ -38,7 +64,6 @@ def connect_to_bucket(cluster: Cluster, bucket_name: str) -> Bucket:
|
|
|
38
64
|
If the operation fails, it will raise an exception.
|
|
39
65
|
"""
|
|
40
66
|
try:
|
|
41
|
-
logger.info(f"Connecting to bucket: {bucket_name}")
|
|
42
67
|
bucket = cluster.bucket(bucket_name)
|
|
43
68
|
return bucket
|
|
44
69
|
except Exception as e:
|
utils/context.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
|
|
4
|
-
from couchbase.cluster import
|
|
4
|
+
from couchbase.cluster import Cluster
|
|
5
5
|
from mcp.server.fastmcp import Context
|
|
6
6
|
|
|
7
|
-
from utils.config import get_settings
|
|
8
|
-
from utils.connection import
|
|
7
|
+
from utils.config import get_settings
|
|
8
|
+
from utils.connection import connect_to_couchbase_cluster
|
|
9
9
|
from utils.constants import MCP_SERVER_NAME
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(f"{MCP_SERVER_NAME}.utils.context")
|
|
@@ -16,7 +16,6 @@ class AppContext:
|
|
|
16
16
|
"""Context for the MCP server."""
|
|
17
17
|
|
|
18
18
|
cluster: Cluster | None = None
|
|
19
|
-
bucket: Bucket | None = None
|
|
20
19
|
read_only_query_mode: bool = True
|
|
21
20
|
|
|
22
21
|
|
|
@@ -30,51 +29,36 @@ def _set_cluster_in_lifespan_context(ctx: Context) -> None:
|
|
|
30
29
|
connection_string = settings.get("connection_string")
|
|
31
30
|
username = settings.get("username")
|
|
32
31
|
password = settings.get("password")
|
|
32
|
+
ca_cert_path = settings.get("ca_cert_path")
|
|
33
|
+
client_cert_path = settings.get("client_cert_path")
|
|
34
|
+
client_key_path = settings.get("client_key_path")
|
|
35
|
+
|
|
33
36
|
cluster = connect_to_couchbase_cluster(
|
|
34
37
|
connection_string, # type: ignore
|
|
35
38
|
username, # type: ignore
|
|
36
39
|
password, # type: ignore
|
|
40
|
+
ca_cert_path,
|
|
41
|
+
client_cert_path,
|
|
42
|
+
client_key_path,
|
|
37
43
|
)
|
|
38
44
|
ctx.request_context.lifespan_context.cluster = cluster
|
|
39
45
|
except Exception as e:
|
|
40
46
|
logger.error(
|
|
41
|
-
|
|
47
|
+
"Failed to connect to Couchbase: %s\n"
|
|
48
|
+
"Verify connection string, and either:\n"
|
|
49
|
+
"- Username/password are correct, or\n"
|
|
50
|
+
"- Client certificate and key exist and match server mapping.\n"
|
|
51
|
+
"If using self-signed or custom CA, set CB_CA_CERT_PATH to the CA file.",
|
|
52
|
+
e,
|
|
42
53
|
)
|
|
43
54
|
raise
|
|
44
55
|
|
|
45
56
|
|
|
46
|
-
def
|
|
47
|
-
"""
|
|
48
|
-
If the bucket is not set, it will try to connect to the bucket using the cluster object in the lifespan context.
|
|
57
|
+
def get_cluster_connection(ctx: Context) -> Cluster:
|
|
58
|
+
"""Return the cluster connection from the lifespan context.
|
|
49
59
|
If the cluster is not set, it will try to connect to the cluster using the connection string, username, and password.
|
|
50
|
-
If the connection fails, it will raise an exception.
|
|
51
60
|
"""
|
|
52
|
-
settings = get_settings()
|
|
53
|
-
bucket_name = settings.get("bucket_name")
|
|
54
|
-
|
|
55
|
-
# If the bucket is not set, try to connect to the bucket using the cluster object in the lifespan context
|
|
56
|
-
app_context = ctx.request_context.lifespan_context
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
# If the cluster is not set, try to connect to the cluster
|
|
60
|
-
if not app_context.cluster:
|
|
61
|
-
_set_cluster_in_lifespan_context(ctx)
|
|
62
|
-
cluster = app_context.cluster
|
|
63
|
-
|
|
64
|
-
# Try to connect to the bucket using the cluster object
|
|
65
|
-
bucket = connect_to_bucket(cluster, bucket_name) # type: ignore
|
|
66
|
-
app_context.bucket = bucket
|
|
67
|
-
except Exception as e:
|
|
68
|
-
logger.error(
|
|
69
|
-
f"Failed to connect to bucket: {e} \n Please check your bucket name and credentials."
|
|
70
|
-
)
|
|
71
|
-
raise
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def ensure_bucket_connection(ctx: Context) -> Bucket:
|
|
75
|
-
"""Ensure bucket connection is established and return the bucket object."""
|
|
76
|
-
validate_connection_config()
|
|
77
61
|
app_context = ctx.request_context.lifespan_context
|
|
78
|
-
if not app_context.
|
|
79
|
-
|
|
80
|
-
return app_context.
|
|
62
|
+
if not app_context.cluster:
|
|
63
|
+
_set_cluster_in_lifespan_context(ctx)
|
|
64
|
+
return app_context.cluster
|