matrixone-python-sdk 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.
Files changed (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. tests/online/test_version_management.py +291 -0
@@ -0,0 +1,425 @@
1
+ # Copyright 2021 - 2022 Matrix Origin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Online tests for SQLAlchemy integration
17
+
18
+ These tests are inspired by example_06_sqlalchemy_integration.py
19
+ """
20
+
21
+ import pytest
22
+ from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, ForeignKey, text
23
+ from sqlalchemy.orm import sessionmaker, relationship, declarative_base
24
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
25
+ from sqlalchemy.pool import QueuePool
26
+ from matrixone import Client, AsyncClient
27
+ from matrixone.logger import create_default_logger
28
+
29
+ # SQLAlchemy setup - will be created per test to avoid conflicts
30
+
31
+
32
+ @pytest.mark.online
33
+ class TestSQLAlchemyIntegration:
34
+ """Test SQLAlchemy integration functionality"""
35
+
36
+ def test_basic_sqlalchemy_operations(self, test_client):
37
+ """Test basic SQLAlchemy operations"""
38
+ # Create SQLAlchemy engine using MatrixOne client
39
+ engine = test_client.get_sqlalchemy_engine()
40
+
41
+ # Create independent Base and models for this test
42
+ Base = declarative_base()
43
+
44
+ class User(Base):
45
+ __tablename__ = 'test_users_basic'
46
+
47
+ id = Column(Integer, primary_key=True)
48
+ username = Column(String(50), unique=True, nullable=False)
49
+ email = Column(String(100), unique=True, nullable=False)
50
+ created_at = Column(DateTime)
51
+
52
+ # Relationship
53
+ posts = relationship("Post", back_populates="author")
54
+
55
+ class Post(Base):
56
+ __tablename__ = 'test_posts_basic'
57
+
58
+ id = Column(Integer, primary_key=True)
59
+ title = Column(String(200), nullable=False)
60
+ content = Column(Text)
61
+ author_id = Column(Integer, ForeignKey('test_users_basic.id'))
62
+ created_at = Column(DateTime)
63
+
64
+ # Relationship
65
+ author = relationship("User", back_populates="posts")
66
+
67
+ # Drop tables if exist and create new ones
68
+ with engine.begin() as conn:
69
+ conn.execute(text('DROP TABLE IF EXISTS test_posts_basic'))
70
+ conn.execute(text('DROP TABLE IF EXISTS test_users_basic'))
71
+
72
+ # Create tables
73
+ Base.metadata.create_all(engine)
74
+
75
+ # Create session
76
+ Session = sessionmaker(bind=engine)
77
+ session = Session()
78
+
79
+ try:
80
+ # Test basic CRUD operations
81
+ # Create user
82
+ user = User(username="testuser", email="test@example.com")
83
+ session.add(user)
84
+ session.commit()
85
+
86
+ # Query user
87
+ found_user = session.query(User).filter_by(username="testuser").first()
88
+ assert found_user is not None
89
+ assert found_user.username == "testuser"
90
+ assert found_user.email == "test@example.com"
91
+
92
+ # Create post
93
+ post = Post(title="Test Post", content="This is a test post", author_id=found_user.id)
94
+ session.add(post)
95
+ session.commit()
96
+
97
+ # Query post with relationship
98
+ found_post = session.query(Post).filter_by(title="Test Post").first()
99
+ assert found_post is not None
100
+ assert found_post.title == "Test Post"
101
+ assert found_post.author.username == "testuser"
102
+
103
+ # Update user
104
+ found_user.email = "updated@example.com"
105
+ session.commit()
106
+
107
+ # Verify update
108
+ updated_user = session.query(User).filter_by(username="testuser").first()
109
+ assert updated_user.email == "updated@example.com"
110
+
111
+ # Delete post
112
+ session.delete(found_post)
113
+ session.commit()
114
+
115
+ # Verify deletion
116
+ deleted_post = session.query(Post).filter_by(title="Test Post").first()
117
+ assert deleted_post is None
118
+
119
+ finally:
120
+ session.close()
121
+ # Cleanup
122
+ Base.metadata.drop_all(engine)
123
+
124
+ def test_sqlalchemy_with_transactions(self, test_client):
125
+ """Test SQLAlchemy with transactions"""
126
+ engine = test_client.get_sqlalchemy_engine()
127
+
128
+ # Create independent Base and models for this test
129
+ Base = declarative_base()
130
+
131
+ class User(Base):
132
+ __tablename__ = 'test_users_transactions'
133
+
134
+ id = Column(Integer, primary_key=True)
135
+ username = Column(String(50), unique=True, nullable=False)
136
+ email = Column(String(100), unique=True, nullable=False)
137
+ created_at = Column(DateTime)
138
+
139
+ # Relationship
140
+ posts = relationship("Post", back_populates="author")
141
+
142
+ class Post(Base):
143
+ __tablename__ = 'test_posts_transactions'
144
+
145
+ id = Column(Integer, primary_key=True)
146
+ title = Column(String(200), nullable=False)
147
+ content = Column(Text)
148
+ author_id = Column(Integer, ForeignKey('test_users_transactions.id'))
149
+ created_at = Column(DateTime)
150
+
151
+ # Relationship
152
+ author = relationship("User", back_populates="posts")
153
+
154
+ # Drop tables if exist and create new ones
155
+ with engine.begin() as conn:
156
+ conn.execute(text('DROP TABLE IF EXISTS test_posts_transactions'))
157
+ conn.execute(text('DROP TABLE IF EXISTS test_users_transactions'))
158
+
159
+ Base.metadata.create_all(engine)
160
+
161
+ Session = sessionmaker(bind=engine)
162
+ session = Session()
163
+
164
+ try:
165
+ # Test transaction rollback
166
+ user = User(username="transaction_user", email="transaction@example.com")
167
+ session.add(user)
168
+ session.flush() # Flush to get ID
169
+
170
+ post = Post(title="Transaction Post", content="This will be rolled back", author_id=user.id)
171
+ session.add(post)
172
+
173
+ # Rollback transaction
174
+ session.rollback()
175
+
176
+ # Verify rollback
177
+ found_user = session.query(User).filter_by(username="transaction_user").first()
178
+ assert found_user is None
179
+
180
+ # Test successful transaction
181
+ user = User(username="success_user", email="success@example.com")
182
+ session.add(user)
183
+ session.commit()
184
+
185
+ # Verify success
186
+ found_user = session.query(User).filter_by(username="success_user").first()
187
+ assert found_user is not None
188
+
189
+ finally:
190
+ session.close()
191
+ Base.metadata.drop_all(engine)
192
+
193
+ def test_sqlalchemy_raw_sql(self, test_client):
194
+ """Test SQLAlchemy with raw SQL"""
195
+ engine = test_client.get_sqlalchemy_engine()
196
+
197
+ # Create independent Base and models for this test
198
+ Base = declarative_base()
199
+
200
+ class User(Base):
201
+ __tablename__ = 'test_users_raw_sql'
202
+
203
+ id = Column(Integer, primary_key=True)
204
+ username = Column(String(50), unique=True, nullable=False)
205
+ email = Column(String(100), unique=True, nullable=False)
206
+ created_at = Column(DateTime)
207
+
208
+ # Drop table if exists and create new one
209
+ with engine.begin() as conn:
210
+ conn.execute(text('DROP TABLE IF EXISTS test_users_raw_sql'))
211
+
212
+ Base.metadata.create_all(engine)
213
+
214
+ Session = sessionmaker(bind=engine)
215
+ session = Session()
216
+
217
+ try:
218
+ # Test raw SQL execution
219
+ result = session.execute(text("SELECT 1 as test_value, USER() as user_info"))
220
+ row = result.fetchone()
221
+ assert row[0] == 1 # test_value
222
+ assert row[1] is not None # user_info
223
+
224
+ # Test raw SQL with parameters
225
+ result = session.execute(text("SELECT :value as param_value"), {"value": 42})
226
+ row = result.fetchone()
227
+ assert row[0] == 42
228
+
229
+ # Test raw SQL for table operations
230
+ session.execute(
231
+ text("INSERT INTO test_users_raw_sql (username, email) VALUES (:username, :email)"),
232
+ {"username": "raw_user", "email": "raw@example.com"},
233
+ )
234
+ session.commit()
235
+
236
+ # Verify insertion
237
+ found_user = session.query(User).filter_by(username="raw_user").first()
238
+ assert found_user is not None
239
+
240
+ finally:
241
+ session.close()
242
+ Base.metadata.drop_all(engine)
243
+
244
+ def test_sqlalchemy_connection_pooling(self, test_client):
245
+ """Test SQLAlchemy connection pooling"""
246
+ # Get the default engine (which already has connection pooling configured)
247
+ engine = test_client.get_sqlalchemy_engine()
248
+
249
+ # Create independent Base and models for this test
250
+ Base = declarative_base()
251
+
252
+ class User(Base):
253
+ __tablename__ = 'test_users_pooling'
254
+
255
+ id = Column(Integer, primary_key=True)
256
+ username = Column(String(50), unique=True, nullable=False)
257
+ email = Column(String(100), unique=True, nullable=False)
258
+ created_at = Column(DateTime)
259
+
260
+ # Drop table if exists and create new one
261
+ with engine.begin() as conn:
262
+ conn.execute(text('DROP TABLE IF EXISTS test_users_pooling'))
263
+
264
+ Base.metadata.create_all(engine)
265
+
266
+ Session = sessionmaker(bind=engine)
267
+
268
+ # Test multiple sessions
269
+ sessions = []
270
+ try:
271
+ for i in range(3):
272
+ session = Session()
273
+ sessions.append(session)
274
+
275
+ user = User(username=f"pool_user_{i}", email=f"pool{i}@example.com")
276
+ session.add(user)
277
+ session.commit()
278
+
279
+ # Verify user was created
280
+ found_user = session.query(User).filter_by(username=f"pool_user_{i}").first()
281
+ assert found_user is not None
282
+
283
+ finally:
284
+ for session in sessions:
285
+ session.close()
286
+ Base.metadata.drop_all(engine)
287
+
288
+ @pytest.mark.asyncio
289
+ async def test_async_sqlalchemy_operations(self, test_async_client):
290
+ """Test async SQLAlchemy operations using models"""
291
+ # Create independent Base and models for this test
292
+ Base = declarative_base()
293
+
294
+ class AsyncUser(Base):
295
+ __tablename__ = 'async_users'
296
+
297
+ id = Column(Integer, primary_key=True, autoincrement=True)
298
+ username = Column(String(50), nullable=False)
299
+ email = Column(String(100), nullable=False)
300
+
301
+ try:
302
+ # Create table using model
303
+ await test_async_client.create_table(AsyncUser)
304
+
305
+ # Clear any existing data using model
306
+ await test_async_client.query(AsyncUser).delete()
307
+
308
+ # Insert user using model
309
+ await test_async_client.batch_insert(AsyncUser, [{"username": "async_user", "email": "async@example.com"}])
310
+
311
+ # Query user using model
312
+ users = await test_async_client.query(AsyncUser).filter(AsyncUser.username == "async_user").all()
313
+ assert len(users) > 0
314
+ assert users[0].username == "async_user"
315
+
316
+ # Update user using model
317
+ await test_async_client.query(AsyncUser).filter(AsyncUser.username == "async_user").update(
318
+ email="updated_async@example.com"
319
+ ).execute()
320
+
321
+ # Verify update using model
322
+ updated_user = await test_async_client.query(AsyncUser).filter(AsyncUser.username == "async_user").first()
323
+ assert updated_user.email == "updated_async@example.com"
324
+
325
+ finally:
326
+ # Cleanup using model
327
+ await test_async_client.drop_table(AsyncUser)
328
+
329
+ def test_sqlalchemy_with_matrixone_features(self, test_client):
330
+ """Test SQLAlchemy with MatrixOne-specific features"""
331
+ engine = test_client.get_sqlalchemy_engine()
332
+
333
+ # Create independent Base and models for this test
334
+ Base = declarative_base()
335
+
336
+ class User(Base):
337
+ __tablename__ = 'test_users_matrixone'
338
+
339
+ id = Column(Integer, primary_key=True)
340
+ username = Column(String(50), unique=True, nullable=False)
341
+ email = Column(String(100), unique=True, nullable=False)
342
+ created_at = Column(DateTime)
343
+
344
+ # Drop table if exists and create new one
345
+ with engine.begin() as conn:
346
+ conn.execute(text('DROP TABLE IF EXISTS test_users_matrixone'))
347
+
348
+ Base.metadata.create_all(engine)
349
+
350
+ Session = sessionmaker(bind=engine)
351
+ session = Session()
352
+
353
+ try:
354
+ # Test MatrixOne-specific SQL through SQLAlchemy
355
+ result = session.execute(text("SHOW DATABASES"))
356
+ databases = result.fetchall()
357
+ assert len(databases) > 0
358
+
359
+ # Test MatrixOne version info
360
+ result = session.execute(text("SELECT VERSION()"))
361
+ version = result.fetchone()
362
+ assert version is not None
363
+ assert "MatrixOne" in version[0] or "mysql" in version[0].lower()
364
+
365
+ # Test MatrixOne user info
366
+ result = session.execute(text("SELECT USER()"))
367
+ user_info = result.fetchone()
368
+ assert user_info is not None
369
+
370
+ finally:
371
+ session.close()
372
+ Base.metadata.drop_all(engine)
373
+
374
+ def test_sqlalchemy_error_handling(self, test_client):
375
+ """Test SQLAlchemy error handling"""
376
+ engine = test_client.get_sqlalchemy_engine()
377
+
378
+ # Create independent Base and models for this test
379
+ Base = declarative_base()
380
+
381
+ class User(Base):
382
+ __tablename__ = 'test_users_errors'
383
+
384
+ id = Column(Integer, primary_key=True)
385
+ username = Column(String(50), unique=True, nullable=False)
386
+ email = Column(String(100), unique=True, nullable=False)
387
+ created_at = Column(DateTime)
388
+
389
+ # Drop table if exists and create new one
390
+ with engine.begin() as conn:
391
+ conn.execute(text('DROP TABLE IF EXISTS test_users_errors'))
392
+
393
+ Base.metadata.create_all(engine)
394
+
395
+ Session = sessionmaker(bind=engine)
396
+ session = Session()
397
+
398
+ try:
399
+ # Test duplicate key error
400
+ user1 = User(username="duplicate_user", email="duplicate1@example.com")
401
+ session.add(user1)
402
+ session.commit()
403
+
404
+ user2 = User(username="duplicate_user", email="duplicate2@example.com")
405
+ session.add(user2)
406
+
407
+ try:
408
+ session.commit()
409
+ assert False, "Should have failed with duplicate key"
410
+ except Exception as e:
411
+ # Expected to fail
412
+ session.rollback()
413
+ assert "duplicate" in str(e).lower() or "unique" in str(e).lower()
414
+
415
+ # Test invalid SQL
416
+ try:
417
+ session.execute(text("INVALID SQL STATEMENT"))
418
+ assert False, "Should have failed with invalid SQL"
419
+ except Exception as e:
420
+ # Expected to fail
421
+ assert "syntax" in str(e).lower() or "error" in str(e).lower()
422
+
423
+ finally:
424
+ session.close()
425
+ Base.metadata.drop_all(engine)