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.
- matrixone/__init__.py +155 -0
- matrixone/account.py +723 -0
- matrixone/async_client.py +3913 -0
- matrixone/async_metadata_manager.py +311 -0
- matrixone/async_orm.py +123 -0
- matrixone/async_vector_index_manager.py +633 -0
- matrixone/base_client.py +208 -0
- matrixone/client.py +4672 -0
- matrixone/config.py +452 -0
- matrixone/connection_hooks.py +286 -0
- matrixone/exceptions.py +89 -0
- matrixone/logger.py +782 -0
- matrixone/metadata.py +820 -0
- matrixone/moctl.py +219 -0
- matrixone/orm.py +2277 -0
- matrixone/pitr.py +646 -0
- matrixone/pubsub.py +771 -0
- matrixone/restore.py +411 -0
- matrixone/search_vector_index.py +1176 -0
- matrixone/snapshot.py +550 -0
- matrixone/sql_builder.py +844 -0
- matrixone/sqlalchemy_ext/__init__.py +161 -0
- matrixone/sqlalchemy_ext/adapters.py +163 -0
- matrixone/sqlalchemy_ext/dialect.py +534 -0
- matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
- matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
- matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
- matrixone/sqlalchemy_ext/ivf_config.py +252 -0
- matrixone/sqlalchemy_ext/table_builder.py +351 -0
- matrixone/sqlalchemy_ext/vector_index.py +1721 -0
- matrixone/sqlalchemy_ext/vector_type.py +948 -0
- matrixone/version.py +580 -0
- matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
- matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
- matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
- matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
- matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +19 -0
- tests/offline/__init__.py +20 -0
- tests/offline/conftest.py +77 -0
- tests/offline/test_account.py +703 -0
- tests/offline/test_async_client_query_comprehensive.py +1218 -0
- tests/offline/test_basic.py +54 -0
- tests/offline/test_case_sensitivity.py +227 -0
- tests/offline/test_connection_hooks_offline.py +287 -0
- tests/offline/test_dialect_schema_handling.py +609 -0
- tests/offline/test_explain_methods.py +346 -0
- tests/offline/test_filter_logical_in.py +237 -0
- tests/offline/test_fulltext_search_comprehensive.py +795 -0
- tests/offline/test_ivf_config.py +249 -0
- tests/offline/test_join_methods.py +281 -0
- tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
- tests/offline/test_logical_in_method.py +237 -0
- tests/offline/test_matrixone_version_parsing.py +264 -0
- tests/offline/test_metadata_offline.py +557 -0
- tests/offline/test_moctl.py +300 -0
- tests/offline/test_moctl_simple.py +251 -0
- tests/offline/test_model_support_offline.py +359 -0
- tests/offline/test_model_support_simple.py +225 -0
- tests/offline/test_pinecone_filter_offline.py +377 -0
- tests/offline/test_pitr.py +585 -0
- tests/offline/test_pubsub.py +712 -0
- tests/offline/test_query_update.py +283 -0
- tests/offline/test_restore.py +445 -0
- tests/offline/test_snapshot_comprehensive.py +384 -0
- tests/offline/test_sql_escaping_edge_cases.py +551 -0
- tests/offline/test_sqlalchemy_integration.py +382 -0
- tests/offline/test_sqlalchemy_vector_integration.py +434 -0
- tests/offline/test_table_builder.py +198 -0
- tests/offline/test_unified_filter.py +398 -0
- tests/offline/test_unified_transaction.py +495 -0
- tests/offline/test_vector_index.py +238 -0
- tests/offline/test_vector_operations.py +688 -0
- tests/offline/test_vector_type.py +174 -0
- tests/offline/test_version_core.py +328 -0
- tests/offline/test_version_management.py +372 -0
- tests/offline/test_version_standalone.py +652 -0
- tests/online/__init__.py +20 -0
- tests/online/conftest.py +216 -0
- tests/online/test_account_management.py +194 -0
- tests/online/test_advanced_features.py +344 -0
- tests/online/test_async_client_interfaces.py +330 -0
- tests/online/test_async_client_online.py +285 -0
- tests/online/test_async_model_insert_online.py +293 -0
- tests/online/test_async_orm_online.py +300 -0
- tests/online/test_async_simple_query_online.py +802 -0
- tests/online/test_async_transaction_simple_query.py +300 -0
- tests/online/test_basic_connection.py +130 -0
- tests/online/test_client_online.py +238 -0
- tests/online/test_config.py +90 -0
- tests/online/test_config_validation.py +123 -0
- tests/online/test_connection_hooks_new_online.py +217 -0
- tests/online/test_dialect_schema_handling_online.py +331 -0
- tests/online/test_filter_logical_in_online.py +374 -0
- tests/online/test_fulltext_comprehensive.py +1773 -0
- tests/online/test_fulltext_label_online.py +433 -0
- tests/online/test_fulltext_search_online.py +842 -0
- tests/online/test_ivf_stats_online.py +506 -0
- tests/online/test_logger_integration.py +311 -0
- tests/online/test_matrixone_query_orm.py +540 -0
- tests/online/test_metadata_online.py +579 -0
- tests/online/test_model_insert_online.py +255 -0
- tests/online/test_mysql_driver_validation.py +213 -0
- tests/online/test_orm_advanced_features.py +2022 -0
- tests/online/test_orm_cte_integration.py +269 -0
- tests/online/test_orm_online.py +270 -0
- tests/online/test_pinecone_filter.py +708 -0
- tests/online/test_pubsub_operations.py +352 -0
- tests/online/test_query_methods.py +225 -0
- tests/online/test_query_update_online.py +433 -0
- tests/online/test_search_vector_index.py +557 -0
- tests/online/test_simple_fulltext_online.py +915 -0
- tests/online/test_snapshot_comprehensive.py +998 -0
- tests/online/test_sqlalchemy_engine_integration.py +336 -0
- tests/online/test_sqlalchemy_integration.py +425 -0
- tests/online/test_transaction_contexts.py +1219 -0
- tests/online/test_transaction_insert_methods.py +356 -0
- tests/online/test_transaction_query_methods.py +288 -0
- tests/online/test_unified_filter_online.py +529 -0
- tests/online/test_vector_comprehensive.py +706 -0
- tests/online/test_version_management.py +291 -0
@@ -0,0 +1,255 @@
|
|
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 model-based insert operations
|
17
|
+
"""
|
18
|
+
|
19
|
+
import pytest
|
20
|
+
import os
|
21
|
+
import sys
|
22
|
+
from datetime import datetime
|
23
|
+
|
24
|
+
# Add the matrixone package to the path
|
25
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
26
|
+
|
27
|
+
from matrixone import Client
|
28
|
+
from sqlalchemy import Column, Integer, String, Text, DateTime, DECIMAL
|
29
|
+
from matrixone.orm import declarative_base
|
30
|
+
from matrixone.exceptions import QueryError
|
31
|
+
from .test_config import online_config
|
32
|
+
|
33
|
+
Base = declarative_base()
|
34
|
+
|
35
|
+
|
36
|
+
class User(Base):
|
37
|
+
"""Test user model for insert operations"""
|
38
|
+
|
39
|
+
__tablename__ = "test_insert_users"
|
40
|
+
|
41
|
+
id = Column(Integer, primary_key=True)
|
42
|
+
name = Column(String(100))
|
43
|
+
email = Column(String(100))
|
44
|
+
age = Column(Integer)
|
45
|
+
created_at = Column(DateTime)
|
46
|
+
|
47
|
+
|
48
|
+
class Product(Base):
|
49
|
+
"""Test product model for insert operations"""
|
50
|
+
|
51
|
+
__tablename__ = "test_insert_products"
|
52
|
+
|
53
|
+
id = Column(Integer, primary_key=True)
|
54
|
+
name = Column(String(200))
|
55
|
+
description = Column(Text)
|
56
|
+
price = Column(DECIMAL(10, 2))
|
57
|
+
category = Column(String(50))
|
58
|
+
in_stock = Column(Integer)
|
59
|
+
|
60
|
+
|
61
|
+
class Order(Base):
|
62
|
+
"""Test order model for insert operations"""
|
63
|
+
|
64
|
+
__tablename__ = "test_insert_orders"
|
65
|
+
|
66
|
+
id = Column(Integer, primary_key=True)
|
67
|
+
user_id = Column(Integer)
|
68
|
+
product_id = Column(Integer)
|
69
|
+
quantity = Column(Integer)
|
70
|
+
total_amount = Column(DECIMAL(10, 2))
|
71
|
+
order_date = Column(DateTime)
|
72
|
+
|
73
|
+
|
74
|
+
class TestModelInsert:
|
75
|
+
"""Test model-based insert operations"""
|
76
|
+
|
77
|
+
@pytest.fixture(scope="class")
|
78
|
+
def test_client(self):
|
79
|
+
"""Create and connect Client for testing"""
|
80
|
+
host, port, user, password, database = online_config.get_connection_params()
|
81
|
+
client = Client()
|
82
|
+
client.connect(host=host, port=port, user=user, password=password, database=database)
|
83
|
+
try:
|
84
|
+
yield client
|
85
|
+
finally:
|
86
|
+
try:
|
87
|
+
client.disconnect()
|
88
|
+
except:
|
89
|
+
pass
|
90
|
+
|
91
|
+
@pytest.fixture(scope="class")
|
92
|
+
def test_database(self, test_client):
|
93
|
+
"""Set up test database and tables"""
|
94
|
+
test_db = "test_model_insert_db"
|
95
|
+
|
96
|
+
try:
|
97
|
+
test_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
|
98
|
+
test_client.execute(f"USE {test_db}")
|
99
|
+
|
100
|
+
# Create test tables using models
|
101
|
+
test_client.create_table(User)
|
102
|
+
test_client.create_table(Product)
|
103
|
+
test_client.create_table(Order)
|
104
|
+
|
105
|
+
yield test_db
|
106
|
+
|
107
|
+
finally:
|
108
|
+
# Clean up
|
109
|
+
try:
|
110
|
+
test_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
|
111
|
+
except Exception as e:
|
112
|
+
print(f"Cleanup failed: {e}")
|
113
|
+
|
114
|
+
def test_single_insert_with_model(self, test_client, test_database):
|
115
|
+
"""Test inserting a single record using model class"""
|
116
|
+
# Insert a single user using model class
|
117
|
+
result = test_client.insert(
|
118
|
+
User, {'id': 1, 'name': 'John Doe', 'email': 'john@example.com', 'age': 30, 'created_at': datetime.now()}
|
119
|
+
)
|
120
|
+
|
121
|
+
assert result.affected_rows == 1
|
122
|
+
|
123
|
+
# Verify the data was inserted correctly
|
124
|
+
users = test_client.query(User).all()
|
125
|
+
assert len(users) == 1
|
126
|
+
assert users[0].name == 'John Doe'
|
127
|
+
assert users[0].email == 'john@example.com'
|
128
|
+
assert users[0].age == 30
|
129
|
+
|
130
|
+
def test_batch_insert_with_model(self, test_client, test_database):
|
131
|
+
"""Test batch inserting multiple records using model class"""
|
132
|
+
# Insert multiple products using model class
|
133
|
+
products_data = [
|
134
|
+
{
|
135
|
+
'id': 1,
|
136
|
+
'name': 'Laptop',
|
137
|
+
'description': 'High-performance laptop',
|
138
|
+
'price': 999.99,
|
139
|
+
'category': 'Electronics',
|
140
|
+
'in_stock': 10,
|
141
|
+
},
|
142
|
+
{
|
143
|
+
'id': 2,
|
144
|
+
'name': 'Book',
|
145
|
+
'description': 'Programming guide',
|
146
|
+
'price': 29.99,
|
147
|
+
'category': 'Education',
|
148
|
+
'in_stock': 50,
|
149
|
+
},
|
150
|
+
{
|
151
|
+
'id': 3,
|
152
|
+
'name': 'Phone',
|
153
|
+
'description': 'Smartphone with AI features',
|
154
|
+
'price': 699.99,
|
155
|
+
'category': 'Electronics',
|
156
|
+
'in_stock': 25,
|
157
|
+
},
|
158
|
+
]
|
159
|
+
|
160
|
+
result = test_client.batch_insert(Product, products_data)
|
161
|
+
assert result.affected_rows == 3
|
162
|
+
|
163
|
+
# Verify the data was inserted correctly
|
164
|
+
products = test_client.query(Product).all()
|
165
|
+
assert len(products) == 3
|
166
|
+
|
167
|
+
# Check specific products
|
168
|
+
laptop = test_client.query(Product).filter_by(name='Laptop').first()
|
169
|
+
assert laptop is not None
|
170
|
+
assert float(laptop.price) == 999.99
|
171
|
+
assert laptop.category == 'Electronics'
|
172
|
+
assert laptop.in_stock == 10
|
173
|
+
|
174
|
+
def test_insert_with_null_values(self, test_client, test_database):
|
175
|
+
"""Test inserting records with NULL values using model class"""
|
176
|
+
# Insert a user with some NULL values
|
177
|
+
result = test_client.insert(
|
178
|
+
User, {'id': 2, 'name': 'Jane Smith', 'email': None, 'age': 25, 'created_at': None} # NULL value # NULL value
|
179
|
+
)
|
180
|
+
|
181
|
+
assert result.affected_rows == 1
|
182
|
+
|
183
|
+
# Verify the data was inserted correctly
|
184
|
+
user = test_client.query(User).filter_by(id=2).first()
|
185
|
+
assert user is not None
|
186
|
+
assert user.name == 'Jane Smith'
|
187
|
+
assert user.email is None
|
188
|
+
assert user.age == 25
|
189
|
+
assert user.created_at is None
|
190
|
+
|
191
|
+
def test_insert_with_different_data_types(self, test_client, test_database):
|
192
|
+
"""Test inserting records with different data types using model class"""
|
193
|
+
# Insert an order with various data types
|
194
|
+
result = test_client.insert(
|
195
|
+
Order,
|
196
|
+
{
|
197
|
+
'id': 1,
|
198
|
+
'user_id': 1,
|
199
|
+
'product_id': 1,
|
200
|
+
'quantity': 2,
|
201
|
+
'total_amount': 1999.98,
|
202
|
+
'order_date': datetime(2024, 1, 15, 10, 30, 0),
|
203
|
+
},
|
204
|
+
)
|
205
|
+
|
206
|
+
assert result.affected_rows == 1
|
207
|
+
|
208
|
+
# Verify the data was inserted correctly
|
209
|
+
order = test_client.query(Order).filter_by(id=1).first()
|
210
|
+
assert order is not None
|
211
|
+
assert order.user_id == 1
|
212
|
+
assert order.product_id == 1
|
213
|
+
assert order.quantity == 2
|
214
|
+
assert float(order.total_amount) == 1999.98
|
215
|
+
assert order.order_date.year == 2024
|
216
|
+
assert order.order_date.month == 1
|
217
|
+
assert order.order_date.day == 15
|
218
|
+
|
219
|
+
def test_insert_error_handling(self, test_client, test_database):
|
220
|
+
"""Test error handling when inserting invalid data using model class"""
|
221
|
+
# Try to insert a user with duplicate primary key
|
222
|
+
with pytest.raises(QueryError):
|
223
|
+
test_client.insert(
|
224
|
+
User,
|
225
|
+
{'id': 1, 'name': 'Duplicate User', 'email': 'duplicate@example.com', 'age': 35}, # Duplicate primary key
|
226
|
+
)
|
227
|
+
|
228
|
+
def test_insert_vs_string_table_name(self, test_client, test_database):
|
229
|
+
"""Test that both model class and string table name work the same way"""
|
230
|
+
# Clear existing data first
|
231
|
+
test_client.execute("DELETE FROM test_insert_users")
|
232
|
+
|
233
|
+
# Insert using model class
|
234
|
+
result1 = test_client.insert(User, {'id': 3, 'name': 'Model User', 'email': 'model@example.com', 'age': 28})
|
235
|
+
|
236
|
+
# Insert using string table name
|
237
|
+
result2 = test_client.insert(
|
238
|
+
'test_insert_users', {'id': 4, 'name': 'String User', 'email': 'string@example.com', 'age': 32}
|
239
|
+
)
|
240
|
+
|
241
|
+
assert result1.affected_rows == 1
|
242
|
+
assert result2.affected_rows == 1
|
243
|
+
|
244
|
+
# Verify both records were inserted
|
245
|
+
users = test_client.query(User).all()
|
246
|
+
assert len(users) == 2 # Only the two we just inserted
|
247
|
+
|
248
|
+
# Check specific users
|
249
|
+
model_user = test_client.query(User).filter_by(name='Model User').first()
|
250
|
+
string_user = test_client.query(User).filter_by(name='String User').first()
|
251
|
+
|
252
|
+
assert model_user is not None
|
253
|
+
assert string_user is not None
|
254
|
+
assert model_user.email == 'model@example.com'
|
255
|
+
assert string_user.email == 'string@example.com'
|
@@ -0,0 +1,213 @@
|
|
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 MySQL driver validation in Client.from_engine() and AsyncClient.from_engine()
|
17
|
+
"""
|
18
|
+
|
19
|
+
import pytest
|
20
|
+
import os
|
21
|
+
import sys
|
22
|
+
from sqlalchemy import create_engine
|
23
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
24
|
+
|
25
|
+
# Add the matrixone package to the path
|
26
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
27
|
+
|
28
|
+
from matrixone import Client, AsyncClient
|
29
|
+
from matrixone.exceptions import ConnectionError
|
30
|
+
from .test_config import online_config
|
31
|
+
|
32
|
+
|
33
|
+
class TestMySQLDriverValidation:
|
34
|
+
"""Online tests for MySQL driver validation"""
|
35
|
+
|
36
|
+
@pytest.fixture(scope="class")
|
37
|
+
def connection_params(self):
|
38
|
+
"""Get connection parameters"""
|
39
|
+
return online_config.get_connection_params()
|
40
|
+
|
41
|
+
def test_client_from_mysql_engine_success(self, connection_params):
|
42
|
+
"""Test creating Client from valid MySQL engine"""
|
43
|
+
host, port, user, password, database = connection_params
|
44
|
+
|
45
|
+
# Create valid MySQL engine
|
46
|
+
connection_string = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}"
|
47
|
+
engine = create_engine(connection_string)
|
48
|
+
|
49
|
+
try:
|
50
|
+
# Should succeed with MySQL engine
|
51
|
+
client = Client.from_engine(engine)
|
52
|
+
assert client.connected() is True
|
53
|
+
|
54
|
+
# Test basic functionality
|
55
|
+
result = client.execute("SELECT 1 as test_value")
|
56
|
+
rows = result.fetchall()
|
57
|
+
assert len(rows) == 1
|
58
|
+
assert rows[0][0] == 1
|
59
|
+
|
60
|
+
finally:
|
61
|
+
client.disconnect()
|
62
|
+
engine.dispose()
|
63
|
+
|
64
|
+
def test_client_from_non_mysql_engine_failure(self):
|
65
|
+
"""Test creating Client from non-MySQL engine fails"""
|
66
|
+
# Test with SQLite engine (should fail) - SQLite doesn't require external drivers
|
67
|
+
engine = create_engine("sqlite:///:memory:")
|
68
|
+
|
69
|
+
try:
|
70
|
+
# Should raise ConnectionError
|
71
|
+
with pytest.raises(ConnectionError) as exc_info:
|
72
|
+
Client.from_engine(engine)
|
73
|
+
|
74
|
+
assert "MatrixOne Client only supports MySQL drivers" in str(exc_info.value)
|
75
|
+
assert "sqlite" in str(exc_info.value).lower()
|
76
|
+
|
77
|
+
finally:
|
78
|
+
engine.dispose()
|
79
|
+
|
80
|
+
def test_client_from_sqlite_engine_failure(self):
|
81
|
+
"""Test creating Client from SQLite engine fails"""
|
82
|
+
# Create SQLite engine (should fail)
|
83
|
+
engine = create_engine("sqlite:///:memory:")
|
84
|
+
|
85
|
+
try:
|
86
|
+
# Should raise ConnectionError
|
87
|
+
with pytest.raises(ConnectionError) as exc_info:
|
88
|
+
Client.from_engine(engine)
|
89
|
+
|
90
|
+
assert "MatrixOne Client only supports MySQL drivers" in str(exc_info.value)
|
91
|
+
assert "sqlite" in str(exc_info.value).lower()
|
92
|
+
|
93
|
+
finally:
|
94
|
+
engine.dispose()
|
95
|
+
|
96
|
+
def test_client_from_mysqlconnector_engine_success(self, connection_params):
|
97
|
+
"""Test creating Client from mysql+mysqlconnector engine"""
|
98
|
+
# Test the driver detection method directly without creating actual engine
|
99
|
+
# since mysql-connector-python might not be installed
|
100
|
+
connection_string = "mysql+mysqlconnector://user:pass@localhost:3306/db"
|
101
|
+
|
102
|
+
# Create a mock engine object to test the detection logic
|
103
|
+
class MockEngine:
|
104
|
+
def __init__(self, url):
|
105
|
+
self.url = url
|
106
|
+
self.dialect = MockDialect()
|
107
|
+
|
108
|
+
class MockDialect:
|
109
|
+
def __init__(self):
|
110
|
+
self.name = "mysql"
|
111
|
+
|
112
|
+
mock_engine = MockEngine(connection_string)
|
113
|
+
|
114
|
+
# Test that our detection method correctly identifies MySQL connector
|
115
|
+
assert Client._is_mysql_engine(mock_engine) is True
|
116
|
+
|
117
|
+
@pytest.mark.asyncio
|
118
|
+
async def test_async_client_from_mysql_engine_success(self, connection_params):
|
119
|
+
"""Test creating AsyncClient from valid MySQL async engine"""
|
120
|
+
host, port, user, password, database = connection_params
|
121
|
+
|
122
|
+
# Create valid MySQL async engine
|
123
|
+
connection_string = f"mysql+aiomysql://{user}:{password}@{host}:{port}/{database}"
|
124
|
+
engine = create_async_engine(connection_string)
|
125
|
+
|
126
|
+
try:
|
127
|
+
# Should succeed with MySQL async engine
|
128
|
+
client = AsyncClient.from_engine(engine)
|
129
|
+
assert client.connected() is True
|
130
|
+
|
131
|
+
# Test basic functionality
|
132
|
+
result = await client.execute("SELECT 3 as test_value")
|
133
|
+
rows = result.fetchall()
|
134
|
+
assert len(rows) == 1
|
135
|
+
assert rows[0][0] == 3
|
136
|
+
|
137
|
+
finally:
|
138
|
+
await client.disconnect()
|
139
|
+
await engine.dispose()
|
140
|
+
|
141
|
+
@pytest.mark.asyncio
|
142
|
+
async def test_async_client_from_non_mysql_engine_failure(self):
|
143
|
+
"""Test creating AsyncClient from non-MySQL async engine fails"""
|
144
|
+
# Test the driver detection method directly without creating actual engine
|
145
|
+
# since aiosqlite might not be installed
|
146
|
+
connection_string = "sqlite+aiosqlite:///:memory:"
|
147
|
+
|
148
|
+
# Create a mock engine object to test the detection logic
|
149
|
+
class MockAsyncEngine:
|
150
|
+
def __init__(self, url):
|
151
|
+
self.url = url
|
152
|
+
self.dialect = MockAsyncDialect()
|
153
|
+
|
154
|
+
class MockAsyncDialect:
|
155
|
+
def __init__(self):
|
156
|
+
self.name = "sqlite"
|
157
|
+
|
158
|
+
mock_engine = MockAsyncEngine(connection_string)
|
159
|
+
|
160
|
+
# Test that our detection method correctly identifies non-MySQL driver
|
161
|
+
assert AsyncClient._is_mysql_async_engine(mock_engine) is False
|
162
|
+
|
163
|
+
def test_client_mysql_driver_detection_methods(self):
|
164
|
+
"""Test the _is_mysql_engine method with various engines"""
|
165
|
+
# Test with MySQL engines (only test pymysql since it's available)
|
166
|
+
mysql_engines = [
|
167
|
+
create_engine("mysql+pymysql://user:pass@localhost:3306/db"),
|
168
|
+
]
|
169
|
+
|
170
|
+
for engine in mysql_engines:
|
171
|
+
assert Client._is_mysql_engine(engine) is True
|
172
|
+
engine.dispose()
|
173
|
+
|
174
|
+
# Test with non-MySQL engines (only test SQLite since it doesn't require external drivers)
|
175
|
+
non_mysql_engines = [
|
176
|
+
create_engine("sqlite:///:memory:"),
|
177
|
+
]
|
178
|
+
|
179
|
+
for engine in non_mysql_engines:
|
180
|
+
assert Client._is_mysql_engine(engine) is False
|
181
|
+
engine.dispose()
|
182
|
+
|
183
|
+
@pytest.mark.asyncio
|
184
|
+
async def test_async_client_mysql_driver_detection_methods(self):
|
185
|
+
"""Test the _is_mysql_async_engine method with various async engines"""
|
186
|
+
# Test with MySQL async engines (only test aiomysql since it's available)
|
187
|
+
mysql_async_engines = [
|
188
|
+
create_async_engine("mysql+aiomysql://user:pass@localhost:3306/db"),
|
189
|
+
]
|
190
|
+
|
191
|
+
for engine in mysql_async_engines:
|
192
|
+
assert AsyncClient._is_mysql_async_engine(engine) is True
|
193
|
+
await engine.dispose()
|
194
|
+
|
195
|
+
# Test with non-MySQL async engines using mock objects
|
196
|
+
# since aiosqlite might not be installed
|
197
|
+
connection_string = "sqlite+aiosqlite:///:memory:"
|
198
|
+
|
199
|
+
class MockAsyncEngine:
|
200
|
+
def __init__(self, url):
|
201
|
+
self.url = url
|
202
|
+
self.dialect = MockAsyncDialect()
|
203
|
+
|
204
|
+
class MockAsyncDialect:
|
205
|
+
def __init__(self):
|
206
|
+
self.name = "sqlite"
|
207
|
+
|
208
|
+
mock_engine = MockAsyncEngine(connection_string)
|
209
|
+
assert AsyncClient._is_mysql_async_engine(mock_engine) is False
|
210
|
+
|
211
|
+
|
212
|
+
if __name__ == "__main__":
|
213
|
+
pytest.main([__file__])
|