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,425 @@
1
+ """
2
+ Client creation and connection tests - testing connection and query execution for all three modes
3
+ Supports configuring connection parameters via environment variables
4
+ """
5
+ import pytest
6
+ import sys
7
+ import os
8
+ import time
9
+ from pathlib import Path
10
+
11
+ # Add project path
12
+ project_root = Path(__file__).parent.parent.parent
13
+ sys.path.insert(0, str(project_root))
14
+
15
+ import seekdbclient
16
+
17
+
18
+ # ==================== Environment Variable Configuration ====================
19
+ # Embedded mode
20
+ SEEKDB_PATH = os.environ.get('SEEKDB_PATH', os.path.join(project_root, "seekdb_store"))
21
+ SEEKDB_DATABASE = os.environ.get('SEEKDB_DATABASE', 'test')
22
+
23
+ # Server mode
24
+ SERVER_HOST = os.environ.get('SERVER_HOST', 'localhost')
25
+ SERVER_PORT = int(os.environ.get('SERVER_PORT', '2881'))
26
+ SERVER_DATABASE = os.environ.get('SERVER_DATABASE', 'test')
27
+ SERVER_USER = os.environ.get('SERVER_USER', 'root')
28
+ SERVER_PASSWORD = os.environ.get('SERVER_PASSWORD', '')
29
+
30
+ # OceanBase mode
31
+ OB_HOST = os.environ.get('OB_HOST', 'localhost')
32
+ OB_PORT = int(os.environ.get('OB_PORT', '11202'))
33
+ OB_TENANT = os.environ.get('OB_TENANT', 'mysql')
34
+ OB_DATABASE = os.environ.get('OB_DATABASE', 'test')
35
+ OB_USER = os.environ.get('OB_USER', 'root')
36
+ OB_PASSWORD = os.environ.get('OB_PASSWORD', '')
37
+
38
+
39
+ class TestClientCreation:
40
+ """Test client creation, connection, and query execution for all three modes"""
41
+
42
+ def _test_collection_management(self, client):
43
+ """
44
+ Common test function for all collection management interfaces
45
+
46
+ Args:
47
+ client: Client proxy object with _server attribute
48
+ """
49
+ # Test 1: create_collection - create a new collection
50
+ test_collection_name = "test_collection_" + str(int(time.time()))
51
+ test_dimension = 128
52
+
53
+ # Create collection
54
+ collection = client.create_collection(
55
+ name=test_collection_name,
56
+ dimension=test_dimension
57
+ )
58
+
59
+ # Verify collection object
60
+ assert collection is not None
61
+ assert collection.name == test_collection_name
62
+ assert collection.dimension == test_dimension
63
+
64
+ # Verify table was created by checking if it exists
65
+ table_name = f"c$v1${test_collection_name}"
66
+ try:
67
+ # Try to describe table structure to verify it exists
68
+ table_info = client._server.execute(f"DESCRIBE `{table_name}`")
69
+ assert table_info is not None
70
+ assert len(table_info) > 0
71
+
72
+ # Verify table has expected columns
73
+ # Handle both dict (server client) and tuple (embedded client) formats
74
+ column_names = []
75
+ column_types = {} # Store column types for verification
76
+ for row in table_info:
77
+ if isinstance(row, dict):
78
+ # Server client returns dict with 'Field' or 'field' key
79
+ field_name = row.get('Field', row.get('field', ''))
80
+ field_type = row.get('Type', row.get('type', ''))
81
+ column_names.append(field_name)
82
+ if field_name:
83
+ column_types[field_name] = str(field_type).lower()
84
+ elif isinstance(row, (tuple, list)):
85
+ # Embedded client returns tuple, first element is field name, second is type
86
+ field_name = row[0] if len(row) > 0 else ''
87
+ field_type = row[1] if len(row) > 1 else ''
88
+ column_names.append(field_name)
89
+ if field_name:
90
+ column_types[field_name] = str(field_type).lower()
91
+ else:
92
+ # Fallback: try to convert to string
93
+ column_names.append(str(row))
94
+
95
+ assert '_id' in column_names
96
+ assert 'document' in column_names
97
+ assert 'embedding' in column_names
98
+ assert 'metadata' in column_names
99
+
100
+ # Verify _id column type is varbinary (for varbinary(512) type)
101
+ if '_id' in column_types:
102
+ id_type = column_types['_id']
103
+ # Check if it's varbinary type (could be 'varbinary(512)' or similar)
104
+ assert 'varbinary' in id_type, f"Expected _id to be varbinary type, but got: {id_type}"
105
+
106
+ print(f"\n✅ Collection '{test_collection_name}' created successfully")
107
+ print(f" Table name: {table_name}")
108
+ print(f" Dimension: {test_dimension}")
109
+ print(f" Table columns: {', '.join(column_names)}")
110
+
111
+ except Exception as e:
112
+ # Clean up and fail
113
+ try:
114
+ client._server.execute(f"DROP TABLE IF EXISTS `{table_name}`")
115
+ except Exception:
116
+ pass
117
+ pytest.fail(f"Failed to verify collection table creation: {e}")
118
+
119
+ # Test 2: get_collection - get the collection we just created
120
+ retrieved_collection = client.get_collection(name=test_collection_name)
121
+ assert retrieved_collection is not None
122
+ assert retrieved_collection.name == test_collection_name
123
+ assert retrieved_collection.dimension == test_dimension
124
+ print(f"\n✅ Collection '{test_collection_name}' retrieved successfully")
125
+ print(f" Collection name: {retrieved_collection.name}")
126
+ print(f" Collection dimension: {retrieved_collection.dimension}")
127
+
128
+ # Test 3: has_collection - should return False for non-existent collection
129
+ non_existent_name = "test_collection_nonexistent_" + str(int(time.time()))
130
+ assert not client.has_collection(non_existent_name)
131
+ print(f"\n✅ has_collection correctly returns False for non-existent collection")
132
+
133
+ # Test 4: has_collection - should return True for existing collection
134
+ assert client.has_collection(test_collection_name)
135
+ print(f"\n✅ has_collection correctly returns True for existing collection")
136
+
137
+ # Test 5: get_or_create_collection - should get existing collection
138
+ existing_collection = client.get_or_create_collection(
139
+ name=test_collection_name,
140
+ dimension=test_dimension
141
+ )
142
+ assert existing_collection is not None
143
+ assert existing_collection.name == test_collection_name
144
+ assert existing_collection.dimension == test_dimension
145
+ print(f"\n✅ get_or_create_collection successfully retrieved existing collection")
146
+
147
+ # Test 6: get_or_create_collection - should create new collection
148
+ test_collection_name_mgmt = "test_collection_mgmt_" + str(int(time.time()))
149
+ new_collection = client.get_or_create_collection(
150
+ name=test_collection_name_mgmt,
151
+ dimension=test_dimension
152
+ )
153
+ assert new_collection is not None
154
+ assert new_collection.name == test_collection_name_mgmt
155
+ assert new_collection.dimension == test_dimension
156
+ print(f"\n✅ get_or_create_collection successfully created collection '{test_collection_name_mgmt}'")
157
+
158
+ # Test 7: list_collections - should include our collections
159
+ collections = client.list_collections()
160
+ assert isinstance(collections, list)
161
+ collection_names = [c.name for c in collections]
162
+ assert test_collection_name in collection_names
163
+ assert test_collection_name_mgmt in collection_names
164
+ print(f"\n✅ list_collections successfully listed collections: {len(collections)} found")
165
+ print(f" Collection names: {collection_names}")
166
+
167
+ # Test 8: delete_collection - should delete the collection
168
+ client.delete_collection(test_collection_name_mgmt)
169
+ assert not client.has_collection(test_collection_name_mgmt)
170
+ print(f"\n✅ delete_collection successfully deleted collection '{test_collection_name_mgmt}'")
171
+
172
+ # Test 9: delete_collection - should raise error for non-existent collection
173
+ try:
174
+ client.delete_collection(test_collection_name_mgmt)
175
+ pytest.fail("delete_collection should raise ValueError for non-existent collection")
176
+ except ValueError as e:
177
+ assert "does not exist" in str(e)
178
+ print(f"\n✅ delete_collection correctly raises ValueError for non-existent collection")
179
+
180
+ # Test 10: get_or_create_collection without dimension - should raise error for non-existent collection
181
+ try:
182
+ client.get_or_create_collection(name="non_existent_collection")
183
+ pytest.fail("get_or_create_collection should raise ValueError when collection doesn't exist and dimension is not provided")
184
+ except ValueError as e:
185
+ assert "dimension parameter is required" in str(e)
186
+ print(f"\n✅ get_or_create_collection correctly raises ValueError when dimension is missing")
187
+
188
+ # Test 11: count_collection - count the number of collections
189
+ collection_count = client.count_collection()
190
+ assert isinstance(collection_count, int)
191
+ assert collection_count >= 1 # At least the test collection we created
192
+ print(f"\n✅ count_collection successfully returned count: {collection_count}")
193
+
194
+ # Test 12: collection.count() - count items in collection (should be 0 for empty collection)
195
+ item_count = collection.count()
196
+ assert isinstance(item_count, int)
197
+ assert item_count == 0 # Collection is empty
198
+ print(f"\n✅ collection.count() successfully returned count: {item_count}")
199
+
200
+ # Test 13: collection.peek() - preview items in empty collection
201
+ preview = collection.peek(limit=5)
202
+ assert preview is not None
203
+ assert len(preview) == 0 # Empty collection
204
+ print(f"\n✅ collection.peek() successfully returned preview: {len(preview)} items")
205
+
206
+ # Add some test data to test count and peek with data
207
+ import uuid
208
+ test_ids = [str(uuid.uuid4()) for _ in range(3)]
209
+ # Use collection dimension for vectors
210
+ test_vectors = [[float(i + j) for j in range(test_dimension)] for i in range(3)]
211
+ collection.add(
212
+ ids=test_ids,
213
+ vectors=test_vectors,
214
+ documents=[f"Test document {i}" for i in range(3)],
215
+ metadatas=[{"index": i} for i in range(3)]
216
+ )
217
+
218
+ # Test 14: collection.count() - count items after adding data
219
+ item_count_after = collection.count()
220
+ assert item_count_after == 3
221
+ print(f"\n✅ collection.count() after adding data: {item_count_after} items")
222
+
223
+ # Test 15: collection.peek() - preview items with data
224
+ preview_with_data = collection.peek(limit=2)
225
+ assert preview_with_data is not None
226
+ assert len(preview_with_data) == 2 # Limited to 2 items
227
+ # Verify preview items have expected fields
228
+ for item in preview_with_data:
229
+ assert hasattr(item, '_id')
230
+ assert hasattr(item, 'document')
231
+ assert hasattr(item, 'metadata')
232
+ print(f"\n✅ collection.peek() with data returned {len(preview_with_data)} items")
233
+
234
+ # Test 16: collection.peek() with different limit
235
+ preview_all = collection.peek(limit=10)
236
+ assert len(preview_all) == 3 # All 3 items
237
+ print(f"\n✅ collection.peek(limit=10) returned {len(preview_all)} items")
238
+
239
+ # Clean up: delete the test collection table
240
+ try:
241
+ client._server.execute(f"DROP TABLE IF EXISTS `{table_name}`")
242
+ print(f" Cleaned up test table: {table_name}")
243
+ except Exception as cleanup_error:
244
+ print(f" Warning: Failed to cleanup test table: {cleanup_error}")
245
+
246
+ def test_create_embedded_client(self):
247
+ """Test creating embedded client (lazy loading) and executing queries"""
248
+ if not os.path.exists(SEEKDB_PATH):
249
+ pytest.fail(
250
+ f"❌ SeekDB data directory does not exist: {SEEKDB_PATH}\n\n"
251
+ f"Solution:\n"
252
+ f" 1. Create the directory: mkdir -p {SEEKDB_PATH}\n"
253
+ f" 2. Or set SEEKDB_PATH environment variable to an existing directory:\n"
254
+ f" export SEEKDB_PATH=/path/to/your/seekdb/data\n"
255
+ f" python3 -m pytest seekdbclient/tests/test_client_creation.py -v -s"
256
+ )
257
+
258
+ # Check if seekdb package is available and properly configured
259
+ try:
260
+ import sys
261
+ project_root_str = str(project_root)
262
+ if project_root_str in sys.path:
263
+ sys.path.remove(project_root_str)
264
+ import seekdb
265
+ if not hasattr(seekdb, 'open') and not hasattr(seekdb, '_initialize_module'):
266
+ pytest.fail(
267
+ "❌ SeekDB embedded package is not properly installed!\n"
268
+ "The 'seekdb' module exists but lacks required methods.\n"
269
+ "Required: 'open' method or '_initialize_module' method\n\n"
270
+ "Solution: Please install the seekdb embedded package from correct source:\n"
271
+ " pip install seekdb\n"
272
+ "Or contact the seekdb package maintainer for installation guide."
273
+ )
274
+ except ImportError:
275
+ pytest.fail(
276
+ "❌ SeekDB embedded package is not installed!\n"
277
+ "The 'seekdb' module cannot be imported.\n\n"
278
+ "Solution: Please install the seekdb embedded package:\n"
279
+ " pip install seekdb\n"
280
+ "Or contact the seekdb package maintainer for installation guide."
281
+ )
282
+ finally:
283
+ if project_root_str not in sys.path:
284
+ sys.path.insert(0, project_root_str)
285
+
286
+ # Create client (returns _ClientProxy)
287
+ client = seekdbclient.Client(
288
+ path=SEEKDB_PATH,
289
+ database=SEEKDB_DATABASE
290
+ )
291
+
292
+ # Verify client type and properties
293
+ assert client is not None
294
+ # Client now returns a proxy
295
+ assert hasattr(client, '_server')
296
+ assert isinstance(client._server, seekdbclient.SeekdbEmbeddedClient)
297
+ assert client._server.mode == "SeekdbEmbeddedClient"
298
+ assert client._server.database == SEEKDB_DATABASE
299
+
300
+ # Should not be connected at this point (lazy loading)
301
+ assert not client._server.is_connected()
302
+
303
+ # Execute query through proxy (first use, triggers connection)
304
+ result = client._server.execute("SELECT 1")
305
+ assert result is not None
306
+ assert len(result) > 0
307
+
308
+ # Should be connected now
309
+ assert client._server.is_connected()
310
+
311
+ print(f"\n✅ Embedded client created and connected successfully: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
312
+ print(f" Query result: {result[0]}")
313
+
314
+ # Test all collection management interfaces
315
+ self._test_collection_management(client)
316
+
317
+ # Automatic cleanup (via __del__)
318
+
319
+ def test_create_server_client(self):
320
+ """Test creating server client (lazy loading) and executing queries"""
321
+ # Create client (returns _ClientProxy)
322
+ client = seekdbclient.Client(
323
+ host=SERVER_HOST,
324
+ port=SERVER_PORT,
325
+ database=SERVER_DATABASE,
326
+ user=SERVER_USER,
327
+ password=SERVER_PASSWORD
328
+ )
329
+
330
+ # Verify client type and properties
331
+ assert client is not None
332
+ assert hasattr(client, '_server')
333
+ assert isinstance(client._server, seekdbclient.SeekdbServerClient)
334
+ assert client._server.mode == "SeekdbServerClient"
335
+ assert client._server.host == SERVER_HOST
336
+ assert client._server.port == SERVER_PORT
337
+ assert client._server.database == SERVER_DATABASE
338
+ assert client._server.user == SERVER_USER
339
+
340
+ # Should not be connected at this point (lazy loading)
341
+ assert not client._server.is_connected()
342
+
343
+ # Execute query through proxy (first use, triggers connection)
344
+ try:
345
+ result = client._server.execute("SELECT 1 as test")
346
+ assert result is not None
347
+ assert len(result) > 0
348
+ assert result[0]['test'] == 1
349
+
350
+ # Should be connected now
351
+ assert client._server.is_connected()
352
+
353
+ print(f"\n✅ Server client created and connected successfully: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
354
+ print(f" Query result: {result[0]}")
355
+
356
+ # Test all collection management interfaces
357
+ self._test_collection_management(client)
358
+
359
+ except Exception as e:
360
+ pytest.fail(f"Server connection failed ({SERVER_HOST}:{SERVER_PORT}): {e}\n"
361
+ f"Hint: Please ensure SeekDB Server is running on port {SERVER_PORT}")
362
+
363
+ # Automatic cleanup (via __del__)
364
+
365
+ def test_create_oceanbase_client(self):
366
+ """Test creating OceanBase client (lazy loading) and executing queries"""
367
+ # Create client (returns _ClientProxy)
368
+ client = seekdbclient.OBClient(
369
+ host=OB_HOST,
370
+ port=OB_PORT,
371
+ tenant=OB_TENANT,
372
+ database=OB_DATABASE,
373
+ user=OB_USER,
374
+ password=OB_PASSWORD
375
+ )
376
+
377
+ # Verify client type and properties
378
+ assert client is not None
379
+ assert hasattr(client, '_server')
380
+ assert isinstance(client._server, seekdbclient.OceanBaseServerClient)
381
+ assert client._server.mode == "OceanBaseServerClient"
382
+ assert client._server.host == OB_HOST
383
+ assert client._server.port == OB_PORT
384
+ assert client._server.tenant == OB_TENANT
385
+ assert client._server.database == OB_DATABASE
386
+ assert client._server.user == OB_USER
387
+ assert client._server.full_user == f"{OB_USER}@{OB_TENANT}"
388
+
389
+ # Should not be connected at this point (lazy loading)
390
+ assert not client._server.is_connected()
391
+
392
+ # Execute query through proxy (first use, triggers connection)
393
+ try:
394
+ result = client._server.execute("SELECT 1 as test")
395
+ assert result is not None
396
+ assert len(result) > 0
397
+ assert result[0]['test'] == 1
398
+
399
+ # Should be connected now
400
+ assert client._server.is_connected()
401
+
402
+ print(f"\n✅ OceanBase client created and connected successfully: {client._server.full_user}@{OB_HOST}:{OB_PORT}/{OB_DATABASE}")
403
+ print(f" Query result: {result[0]}")
404
+
405
+ # Test all collection management interfaces
406
+ self._test_collection_management(client)
407
+
408
+ except Exception as e:
409
+ pytest.fail(f"OceanBase connection failed ({OB_HOST}:{OB_PORT}): {e}\n"
410
+ f"Hint: Please ensure OceanBase is running and tenant '{OB_TENANT}' is created")
411
+
412
+ # Automatic cleanup (via __del__)
413
+
414
+
415
+ if __name__ == "__main__":
416
+ print("\n" + "="*60)
417
+ print("SeekDBClient - Client Creation and Connection Tests")
418
+ print("="*60)
419
+ print(f"\nEnvironment Variable Configuration:")
420
+ print(f" Embedded mode: path={SEEKDB_PATH}, database={SEEKDB_DATABASE}")
421
+ print(f" Server mode: {SERVER_USER}@{SERVER_HOST}:{SERVER_PORT}/{SERVER_DATABASE}")
422
+ print(f" OceanBase mode: {OB_USER}@{OB_TENANT} -> {OB_HOST}:{OB_PORT}/{OB_DATABASE}")
423
+ print("="*60 + "\n")
424
+
425
+ pytest.main([__file__, "-v", "-s"])