pyseekdb 0.1.0.dev3__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.
@@ -0,0 +1,226 @@
1
+ """
2
+ Server mode client - based on pymysql
3
+ """
4
+ import logging
5
+ from typing import Any, List, Optional, Sequence, Dict, Union
6
+
7
+ import pymysql
8
+ from pymysql.cursors import DictCursor
9
+
10
+ from .client_base import BaseClient
11
+ from .collection import Collection
12
+ from .database import Database
13
+ from .admin_client import DEFAULT_TENANT
14
+ from .query_result import QueryResult, QueryResultItem
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class SeekdbServerClient(BaseClient):
20
+ """SeekDB server mode client (connecting via pymysql, lazy loading)"""
21
+
22
+ def __init__(
23
+ self,
24
+ host: str = "localhost",
25
+ port: int = 2882,
26
+ database: str = "test",
27
+ user: str = "root",
28
+ password: str = "",
29
+ charset: str = "utf8mb4",
30
+ **kwargs
31
+ ):
32
+ """
33
+ Initialize server mode client (no immediate connection)
34
+
35
+ Args:
36
+ host: server address
37
+ port: server port
38
+ database: database name
39
+ user: username
40
+ password: password
41
+ charset: charset
42
+ """
43
+ self.host = host
44
+ self.port = port
45
+ self.database = database
46
+ self.user = user
47
+ self.password = password
48
+ self.charset = charset
49
+ self.kwargs = kwargs
50
+ self._connection = None
51
+
52
+ logger.info(
53
+ f"Initialize SeekdbServerClient: {self.user}@{self.host}:{self.port}/{self.database}"
54
+ )
55
+
56
+ # ==================== Connection Management ====================
57
+
58
+ def _ensure_connection(self) -> pymysql.Connection:
59
+ """Ensure connection is established (internal method)"""
60
+ if self._connection is None or not self._connection.open:
61
+ self._connection = pymysql.connect(
62
+ host=self.host,
63
+ port=self.port,
64
+ user=self.user,
65
+ password=self.password,
66
+ database=self.database,
67
+ charset=self.charset,
68
+ cursorclass=DictCursor,
69
+ **self.kwargs
70
+ )
71
+ logger.info(f"✅ Connected to server: {self.host}:{self.port}/{self.database}")
72
+
73
+ return self._connection
74
+
75
+ def _cleanup(self):
76
+ """Internal cleanup method: close connection)"""
77
+ if self._connection is not None:
78
+ self._connection.close()
79
+ self._connection = None
80
+ logger.info("Connection closed")
81
+
82
+ def is_connected(self) -> bool:
83
+ """Check connection status"""
84
+ return self._connection is not None and self._connection.open
85
+
86
+ def execute(self, sql: str) -> Any:
87
+ conn = self._ensure_connection()
88
+
89
+ with conn.cursor() as cursor:
90
+ cursor.execute(sql)
91
+
92
+ sql_upper = sql.strip().upper()
93
+ if (sql_upper.startswith('SELECT') or
94
+ sql_upper.startswith('SHOW') or
95
+ sql_upper.startswith('DESCRIBE') or
96
+ sql_upper.startswith('DESC')):
97
+ return cursor.fetchall()
98
+
99
+ conn.commit()
100
+ return cursor
101
+
102
+ def get_raw_connection(self) -> pymysql.Connection:
103
+ """Get raw connection object"""
104
+ return self._ensure_connection()
105
+
106
+ @property
107
+ def mode(self) -> str:
108
+ return "SeekdbServerClient"
109
+
110
+ # ==================== Collection Management (framework) ====================
111
+
112
+ # create_collection is inherited from BaseClient - no override needed
113
+ # get_collection is inherited from BaseClient - no override needed
114
+ # delete_collection is inherited from BaseClient - no override needed
115
+ # list_collections is inherited from BaseClient - no override needed
116
+ # has_collection is inherited from BaseClient - no override needed
117
+
118
+ # ==================== Collection Internal Operations ====================
119
+ # These methods are called by Collection objects
120
+
121
+ # -------------------- DML Operations --------------------
122
+ # _collection_add is inherited from BaseClient
123
+ # _collection_update is inherited from BaseClient
124
+ # _collection_upsert is inherited from BaseClient
125
+ # _collection_delete is inherited from BaseClient
126
+
127
+ # -------------------- DQL Operations --------------------
128
+ # Note: _collection_query() and _collection_get() use base class implementation
129
+
130
+ # _collection_hybrid_search is inherited from BaseClient
131
+
132
+ # -------------------- Collection Info --------------------
133
+
134
+ # _collection_count is inherited from BaseClient - no override needed
135
+
136
+ # ==================== Database Management ====================
137
+
138
+ def create_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
139
+ """
140
+ Create database (tenant parameter ignored for server mode)
141
+
142
+ Args:
143
+ name: database name
144
+ tenant: ignored for server mode (no tenant concept)
145
+ """
146
+ logger.info(f"Creating database: {name}")
147
+ sql = f"CREATE DATABASE IF NOT EXISTS `{name}`"
148
+ self.execute(sql)
149
+ logger.info(f"✅ Database created: {name}")
150
+
151
+ def get_database(self, name: str, tenant: str = DEFAULT_TENANT) -> Database:
152
+ """
153
+ Get database object (tenant parameter ignored for server mode)
154
+
155
+ Args:
156
+ name: database name
157
+ tenant: ignored for server mode (no tenant concept)
158
+ """
159
+ logger.info(f"Getting database: {name}")
160
+ sql = f"SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '{name}'"
161
+ result = self.execute(sql)
162
+
163
+ if not result:
164
+ raise ValueError(f"Database not found: {name}")
165
+
166
+ row = result[0]
167
+ return Database(
168
+ name=row['SCHEMA_NAME'],
169
+ tenant=None, # No tenant concept in server mode
170
+ charset=row['DEFAULT_CHARACTER_SET_NAME'],
171
+ collation=row['DEFAULT_COLLATION_NAME']
172
+ )
173
+
174
+ def delete_database(self, name: str, tenant: str = DEFAULT_TENANT) -> None:
175
+ """
176
+ Delete database (tenant parameter ignored for server mode)
177
+
178
+ Args:
179
+ name: database name
180
+ tenant: ignored for server mode (no tenant concept)
181
+ """
182
+ logger.info(f"Deleting database: {name}")
183
+ sql = f"DROP DATABASE IF EXISTS `{name}`"
184
+ self.execute(sql)
185
+ logger.info(f"✅ Database deleted: {name}")
186
+
187
+ def list_databases(
188
+ self,
189
+ limit: Optional[int] = None,
190
+ offset: Optional[int] = None,
191
+ tenant: str = DEFAULT_TENANT
192
+ ) -> Sequence[Database]:
193
+ """
194
+ List all databases (tenant parameter ignored for server mode)
195
+
196
+ Args:
197
+ limit: maximum number of results to return
198
+ offset: number of results to skip
199
+ tenant: ignored for server mode (no tenant concept)
200
+ """
201
+ logger.info("Listing databases")
202
+ sql = "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA"
203
+
204
+ if limit is not None:
205
+ if offset is not None:
206
+ sql += f" LIMIT {offset}, {limit}"
207
+ else:
208
+ sql += f" LIMIT {limit}"
209
+
210
+ result = self.execute(sql)
211
+
212
+ databases = []
213
+ for row in result:
214
+ databases.append(Database(
215
+ name=row['SCHEMA_NAME'],
216
+ tenant=None, # No tenant concept in server mode
217
+ charset=row['DEFAULT_CHARACTER_SET_NAME'],
218
+ collation=row['DEFAULT_COLLATION_NAME']
219
+ ))
220
+
221
+ logger.info(f"✅ Found {len(databases)} databases")
222
+ return databases
223
+
224
+ def __repr__(self):
225
+ status = "connected" if self.is_connected() else "disconnected"
226
+ return f"<SeekdbServerClient {self.user}@{self.host}:{self.port}/{self.database} status={status}>"