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,352 @@
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 PubSub operations
17
+
18
+ These tests are inspired by example_08_pubsub_operations.py
19
+ """
20
+
21
+ import pytest
22
+ import time
23
+ from datetime import datetime
24
+ from matrixone import Client, AsyncClient
25
+ from matrixone.logger import create_default_logger
26
+ from .test_config import online_config
27
+
28
+
29
+ @pytest.mark.online
30
+ class TestPubSubOperations:
31
+ """Test PubSub operations functionality"""
32
+
33
+ def test_basic_pubsub_operations(self, test_client):
34
+ """Test basic PubSub operations within the same account"""
35
+ # Create test database and tables
36
+ test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_test")
37
+ test_client.execute("USE pubsub_test")
38
+
39
+ test_client.execute(
40
+ """
41
+ CREATE TABLE IF NOT EXISTS products (
42
+ id INT PRIMARY KEY AUTO_INCREMENT,
43
+ name VARCHAR(100) NOT NULL,
44
+ price DECIMAL(10,2),
45
+ category VARCHAR(50),
46
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47
+ )
48
+ """
49
+ )
50
+
51
+ test_client.execute(
52
+ """
53
+ CREATE TABLE IF NOT EXISTS orders (
54
+ id INT PRIMARY KEY AUTO_INCREMENT,
55
+ product_id INT,
56
+ quantity INT,
57
+ total_price DECIMAL(10,2),
58
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
59
+ )
60
+ """
61
+ )
62
+
63
+ try:
64
+ # Test 1: Basic PubSub setup (as per examples)
65
+ # Insert sample data
66
+ products_data = [
67
+ ("Laptop", 999.99, "Electronics"),
68
+ ("Mouse", 29.99, "Electronics"),
69
+ ("Keyboard", 79.99, "Electronics"),
70
+ ("Monitor", 299.99, "Electronics"),
71
+ ]
72
+
73
+ for name, price, category in products_data:
74
+ test_client.execute(f"INSERT INTO products (name, price, category) VALUES ('{name}', {price}, '{category}')")
75
+
76
+ # Test 2: List publications (as per examples - should work even if empty)
77
+ publications = test_client.pubsub.list_publications()
78
+ assert isinstance(publications, list)
79
+
80
+ # Test 3: List subscriptions (as per examples - should work even if empty)
81
+ subscriptions = test_client.pubsub.list_subscriptions()
82
+ assert isinstance(subscriptions, list)
83
+
84
+ # Test 4: Data operations
85
+ test_client.execute("INSERT INTO orders (product_id, quantity, total_price) VALUES (1, 2, 199.98)")
86
+
87
+ # Verify data
88
+ result = test_client.execute("SELECT COUNT(*) FROM products")
89
+ assert result.rows[0][0] == 4
90
+
91
+ result = test_client.execute("SELECT COUNT(*) FROM orders")
92
+ assert result.rows[0][0] == 1
93
+
94
+ finally:
95
+ # Cleanup
96
+ try:
97
+ test_client.pubsub.drop_subscription("product_subscription")
98
+ test_client.pubsub.drop_publication("product_updates")
99
+ except Exception as e:
100
+ print(f"Warning: Failed to cleanup pubsub resources: {e}")
101
+ # Don't ignore - this could indicate resource leaks
102
+
103
+ test_client.execute("DROP DATABASE IF EXISTS pubsub_test")
104
+
105
+ def test_pubsub_with_multiple_tables(self, test_client):
106
+ """Test PubSub with multiple tables"""
107
+ # Create test database
108
+ test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_multi_test")
109
+ test_client.execute("USE pubsub_multi_test")
110
+
111
+ # Create multiple tables
112
+ test_client.execute(
113
+ """
114
+ CREATE TABLE IF NOT EXISTS users (
115
+ id INT PRIMARY KEY AUTO_INCREMENT,
116
+ username VARCHAR(50) NOT NULL,
117
+ email VARCHAR(100) NOT NULL,
118
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
119
+ )
120
+ """
121
+ )
122
+
123
+ test_client.execute(
124
+ """
125
+ CREATE TABLE IF NOT EXISTS posts (
126
+ id INT PRIMARY KEY AUTO_INCREMENT,
127
+ user_id INT,
128
+ title VARCHAR(200) NOT NULL,
129
+ content TEXT,
130
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
131
+ )
132
+ """
133
+ )
134
+
135
+ test_client.execute(
136
+ """
137
+ CREATE TABLE IF NOT EXISTS comments (
138
+ id INT PRIMARY KEY AUTO_INCREMENT,
139
+ post_id INT,
140
+ user_id INT,
141
+ content TEXT,
142
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
143
+ )
144
+ """
145
+ )
146
+
147
+ try:
148
+ # Test multi-table PubSub operations (as per examples)
149
+ # Insert sample data
150
+ test_client.execute("INSERT INTO users (username, email) VALUES ('testuser', 'test@example.com')")
151
+ test_client.execute("INSERT INTO posts (user_id, title, content) VALUES (1, 'Test Post', 'This is a test post')")
152
+ test_client.execute("INSERT INTO comments (post_id, user_id, content) VALUES (1, 1, 'This is a test comment')")
153
+
154
+ # Test list operations (as per examples - should work even if empty)
155
+ publications = test_client.pubsub.list_publications()
156
+ assert isinstance(publications, list)
157
+
158
+ subscriptions = test_client.pubsub.list_subscriptions()
159
+ assert isinstance(subscriptions, list)
160
+
161
+ # Verify data across all tables
162
+ result = test_client.execute("SELECT COUNT(*) FROM users")
163
+ assert result.rows[0][0] == 1
164
+
165
+ result = test_client.execute("SELECT COUNT(*) FROM posts")
166
+ assert result.rows[0][0] == 1
167
+
168
+ result = test_client.execute("SELECT COUNT(*) FROM comments")
169
+ assert result.rows[0][0] == 1
170
+
171
+ finally:
172
+ # Cleanup
173
+ try:
174
+ test_client.pubsub.drop_publication("multi_table_publication")
175
+ except Exception as e:
176
+ print(f"Warning: Failed to cleanup multi-table publication: {e}")
177
+
178
+ test_client.execute("DROP DATABASE IF EXISTS pubsub_multi_test")
179
+
180
+ def test_pubsub_error_handling(self, test_client):
181
+ """Test PubSub error handling"""
182
+ # Test creating publication with invalid parameters
183
+ try:
184
+ test_client.pubsub.create_database_publication(
185
+ name="", # Empty name should fail
186
+ database="nonexistent_db",
187
+ account=online_config.get_test_account(),
188
+ )
189
+ assert False, "Should have failed with empty name"
190
+ except Exception:
191
+ pass # Expected to fail
192
+
193
+ # Test creating subscription with invalid parameters
194
+ try:
195
+ test_client.pubsub.create_subscription(
196
+ name="", # Empty name should fail
197
+ publication="nonexistent_publication",
198
+ account=online_config.get_test_account(),
199
+ )
200
+ assert False, "Should have failed with empty name"
201
+ except Exception:
202
+ pass # Expected to fail
203
+
204
+ # Test dropping non-existent publication
205
+ try:
206
+ test_client.pubsub.drop_publication("nonexistent_publication")
207
+ assert False, "Should have failed with non-existent publication"
208
+ except Exception:
209
+ pass # Expected to fail
210
+
211
+ # Test dropping non-existent subscription
212
+ try:
213
+ test_client.pubsub.drop_subscription("nonexistent_subscription")
214
+ assert False, "Should have failed with non-existent subscription"
215
+ except Exception:
216
+ pass # Expected to fail
217
+
218
+ def test_pubsub_listing_operations(self, test_client):
219
+ """Test PubSub listing operations"""
220
+ # Create test database
221
+ test_client.execute("CREATE DATABASE IF NOT EXISTS pubsub_list_test")
222
+ test_client.execute("USE pubsub_list_test")
223
+
224
+ test_client.execute(
225
+ """
226
+ CREATE TABLE IF NOT EXISTS test_table (
227
+ id INT PRIMARY KEY AUTO_INCREMENT,
228
+ name VARCHAR(50) NOT NULL
229
+ )
230
+ """
231
+ )
232
+
233
+ try:
234
+ # Test listing operations (as per examples - should work even if empty)
235
+ # Test listing publications
236
+ publications = test_client.pubsub.list_publications()
237
+ assert isinstance(publications, list)
238
+
239
+ # Test listing subscriptions
240
+ subscriptions = test_client.pubsub.list_subscriptions()
241
+ assert isinstance(subscriptions, list)
242
+
243
+ # Insert test data
244
+ test_client.execute("INSERT INTO test_table (name) VALUES ('test_item')")
245
+
246
+ # Verify data
247
+ result = test_client.execute("SELECT COUNT(*) FROM test_table")
248
+ assert result.rows[0][0] == 1
249
+
250
+ finally:
251
+ # Cleanup
252
+ try:
253
+ test_client.pubsub.drop_subscription("list_test_subscription")
254
+ test_client.pubsub.drop_publication("list_test_publication")
255
+ except Exception as e:
256
+ print(f"Warning: Failed to cleanup list test resources: {e}")
257
+
258
+ test_client.execute("DROP DATABASE IF EXISTS pubsub_list_test")
259
+
260
+ @pytest.mark.asyncio
261
+ async def test_async_pubsub_operations(self, test_async_client):
262
+ """Test async PubSub operations"""
263
+ # Create test database
264
+ await test_async_client.execute("CREATE DATABASE IF NOT EXISTS async_pubsub_test")
265
+ await test_async_client.execute("USE async_pubsub_test")
266
+
267
+ await test_async_client.execute(
268
+ """
269
+ CREATE TABLE IF NOT EXISTS async_test_table (
270
+ id INT PRIMARY KEY AUTO_INCREMENT,
271
+ name VARCHAR(50) NOT NULL,
272
+ value INT
273
+ )
274
+ """
275
+ )
276
+
277
+ try:
278
+ # Test async PubSub operations (as per examples)
279
+ # Test list operations (should work even if empty)
280
+ publications = await test_async_client.pubsub.list_publications()
281
+ assert isinstance(publications, list)
282
+
283
+ subscriptions = await test_async_client.pubsub.list_subscriptions()
284
+ assert isinstance(subscriptions, list)
285
+
286
+ # Test async data operations
287
+ await test_async_client.execute("INSERT INTO async_test_table (name, value) VALUES ('async_test', 42)")
288
+
289
+ # Verify data
290
+ result = await test_async_client.execute("SELECT COUNT(*) FROM async_test_table")
291
+ assert result.rows[0][0] == 1
292
+
293
+ finally:
294
+ # Cleanup
295
+ try:
296
+ await test_async_client.pubsub.drop_subscription("async_test_subscription")
297
+ await test_async_client.pubsub.drop_publication("async_test_publication")
298
+ except Exception as e:
299
+ print(f"Warning: Failed to cleanup async pubsub resources: {e}")
300
+
301
+ await test_async_client.execute("DROP DATABASE IF EXISTS async_pubsub_test")
302
+
303
+ def test_pubsub_with_logging(self, connection_params):
304
+ """Test PubSub operations with custom logging"""
305
+ host, port, user, password, database = connection_params
306
+
307
+ # Create logger
308
+ logger = create_default_logger()
309
+
310
+ # Create client with logging
311
+ client = Client()
312
+ client.connect(host=host, port=port, user=user, password=password, database=database)
313
+
314
+ try:
315
+ # Create test database
316
+ client.execute("CREATE DATABASE IF NOT EXISTS pubsub_log_test")
317
+ client.execute("USE pubsub_log_test")
318
+
319
+ client.execute(
320
+ """
321
+ CREATE TABLE IF NOT EXISTS log_test_table (
322
+ id INT PRIMARY KEY AUTO_INCREMENT,
323
+ name VARCHAR(50) NOT NULL
324
+ )
325
+ """
326
+ )
327
+
328
+ # Test PubSub operations with logging (as per examples)
329
+ # Test list operations (should work even if empty)
330
+ publications = client.pubsub.list_publications()
331
+ assert isinstance(publications, list)
332
+
333
+ subscriptions = client.pubsub.list_subscriptions()
334
+ assert isinstance(subscriptions, list)
335
+
336
+ # Test data operations with logging
337
+ client.execute("INSERT INTO log_test_table (name) VALUES ('log_test')")
338
+
339
+ # Verify data
340
+ result = client.execute("SELECT COUNT(*) FROM log_test_table")
341
+ assert result.rows[0][0] == 1
342
+
343
+ # Cleanup
344
+ try:
345
+ client.pubsub.drop_publication("log_test_publication")
346
+ except Exception as e:
347
+ print(f"Warning: Failed to cleanup logging test publication: {e}")
348
+
349
+ client.execute("DROP DATABASE IF EXISTS pubsub_log_test")
350
+
351
+ finally:
352
+ client.disconnect()
@@ -0,0 +1,225 @@
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
+ Test cases for SQLAlchemy-style query methods (all, one, first, scalar, one_or_none)
17
+ """
18
+
19
+ import pytest
20
+ from sqlalchemy import Column, Integer, String, create_engine
21
+ from sqlalchemy.exc import NoResultFound, MultipleResultsFound
22
+
23
+ from matrixone import Client
24
+ from matrixone.orm import declarative_base
25
+ from .test_config import online_config
26
+
27
+ Base = declarative_base()
28
+
29
+
30
+ class QueryTestUser(Base):
31
+ __tablename__ = 'test_query_users'
32
+ id = Column(Integer, primary_key=True)
33
+ name = Column(String(50))
34
+ age = Column(Integer)
35
+
36
+
37
+ class TestQueryMethods:
38
+ """Test SQLAlchemy-style query methods"""
39
+
40
+ @pytest.fixture(autouse=True)
41
+ def setup_and_cleanup(self):
42
+ """Setup test data and cleanup after tests"""
43
+ # Setup
44
+ self.client = Client()
45
+ host, port, user, password, database = online_config.get_connection_params()
46
+ self.client.connect(host=host, port=port, user=user, password=password, database=database)
47
+
48
+ # Create table (with checkfirst to avoid errors if table exists)
49
+ try:
50
+ self.client.create_table(QueryTestUser)
51
+ except Exception:
52
+ # Table might already exist, try to drop and recreate
53
+ try:
54
+ self.client.drop_table(QueryTestUser)
55
+ self.client.create_table(QueryTestUser)
56
+ except Exception:
57
+ pass # Continue with existing table
58
+
59
+ # Insert test data
60
+ users_data = [
61
+ {"id": 1, "name": "Alice", "age": 25},
62
+ {"id": 2, "name": "Bob", "age": 30},
63
+ {"id": 3, "name": "Charlie", "age": 35},
64
+ ]
65
+ for user_data in users_data:
66
+ self.client.insert(QueryTestUser.__tablename__, user_data)
67
+
68
+ yield
69
+
70
+ # Cleanup
71
+ try:
72
+ self.client.drop_table(QueryTestUser)
73
+ except:
74
+ pass
75
+ self.client.disconnect()
76
+
77
+ def test_all_method(self):
78
+ """Test all() method returns all results"""
79
+ results = self.client.query(QueryTestUser).all()
80
+ assert len(results) == 3
81
+ assert all(isinstance(user, QueryTestUser) for user in results)
82
+ assert results[0].name == 'Alice'
83
+ assert results[1].name == 'Bob'
84
+ assert results[2].name == 'Charlie'
85
+
86
+ def test_first_method(self):
87
+ """Test first() method returns first result or None"""
88
+ # Test with results
89
+ result = self.client.query(QueryTestUser).first()
90
+ assert isinstance(result, QueryTestUser)
91
+ assert result.name == 'Alice'
92
+
93
+ # Test with no results
94
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).first()
95
+ assert result is None
96
+
97
+ def test_one_method_success(self):
98
+ """Test one() method with exactly one result"""
99
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one()
100
+ assert isinstance(result, QueryTestUser)
101
+ assert result.name == 'Alice'
102
+
103
+ def test_one_method_no_results(self):
104
+ """Test one() method with no results raises NoResultFound"""
105
+ with pytest.raises(NoResultFound):
106
+ self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one()
107
+
108
+ def test_one_method_multiple_results(self):
109
+ """Test one() method with multiple results raises MultipleResultsFound"""
110
+ with pytest.raises(MultipleResultsFound):
111
+ self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one()
112
+
113
+ def test_one_or_none_method_success(self):
114
+ """Test one_or_none() method with exactly one result"""
115
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one_or_none()
116
+ assert isinstance(result, QueryTestUser)
117
+ assert result.name == 'Alice'
118
+
119
+ def test_one_or_none_method_no_results(self):
120
+ """Test one_or_none() method with no results returns None"""
121
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one_or_none()
122
+ assert result is None
123
+
124
+ def test_one_or_none_method_multiple_results(self):
125
+ """Test one_or_none() method with multiple results raises MultipleResultsFound"""
126
+ with pytest.raises(MultipleResultsFound):
127
+ self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one_or_none()
128
+
129
+ def test_scalar_method_with_model(self):
130
+ """Test scalar() method returns first column value from model"""
131
+ # Test with primary key (first column)
132
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).scalar()
133
+ assert result == 1
134
+
135
+ # Test with no results
136
+ result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).scalar()
137
+ assert result is None
138
+
139
+ def test_scalar_method_with_select_columns(self):
140
+ """Test scalar() method with custom select columns"""
141
+ # Select specific column
142
+ result = self.client.query(QueryTestUser).select(QueryTestUser.name).filter(QueryTestUser.id == 1).scalar()
143
+ assert result == 'Alice'
144
+
145
+ # Select count - use count() method directly, not with select
146
+ result = self.client.query(QueryTestUser).count()
147
+ assert result == 3
148
+
149
+ def test_scalar_method_with_aggregate(self):
150
+ """Test scalar() method with aggregate functions"""
151
+ from sqlalchemy import func
152
+
153
+ # Count all users
154
+ result = self.client.query(QueryTestUser).select(func.count(QueryTestUser.id)).scalar()
155
+ assert result == 3
156
+
157
+ # Average age
158
+ result = self.client.query(QueryTestUser).select(func.avg(QueryTestUser.age)).scalar()
159
+ assert result == 30.0 # (25 + 30 + 35) / 3
160
+
161
+ def test_methods_with_filters(self):
162
+ """Test query methods work with various filters"""
163
+ # Test with multiple filters - use AND to combine conditions
164
+ from sqlalchemy import and_
165
+
166
+ result = self.client.query(QueryTestUser).filter(and_(QueryTestUser.age > 25, QueryTestUser.name.like('B%'))).one()
167
+ assert result.name == 'Bob'
168
+ assert result.age == 30
169
+
170
+ def test_methods_with_ordering(self):
171
+ """Test query methods work with ordering"""
172
+ # Test first() with ordering
173
+ result = self.client.query(QueryTestUser).order_by(QueryTestUser.age.desc()).first()
174
+ assert result.name == 'Charlie'
175
+ assert result.age == 35
176
+
177
+ # Test scalar() with ordering
178
+ result = self.client.query(QueryTestUser).select(QueryTestUser.name).order_by(QueryTestUser.age.desc()).scalar()
179
+ assert result == 'Charlie'
180
+
181
+ def test_methods_with_limit(self):
182
+ """Test query methods work with limit"""
183
+ # Test all() with limit
184
+ results = self.client.query(QueryTestUser).limit(2).all()
185
+ assert len(results) == 2
186
+
187
+ # Test first() with limit (should still return only one)
188
+ result = self.client.query(QueryTestUser).limit(2).first()
189
+ assert isinstance(result, QueryTestUser)
190
+
191
+ def test_count_method(self):
192
+ """Test count() method"""
193
+ # Count all
194
+ count = self.client.query(QueryTestUser).count()
195
+ assert count == 3
196
+
197
+ # Count with filter
198
+ count = self.client.query(QueryTestUser).filter(QueryTestUser.age > 25).count()
199
+ assert count == 2
200
+
201
+ def test_methods_consistency(self):
202
+ """Test that methods are consistent with each other"""
203
+ # Test that first() and all()[0] return same result
204
+ first_result = self.client.query(QueryTestUser).order_by(QueryTestUser.id).first()
205
+ all_results = self.client.query(QueryTestUser).order_by(QueryTestUser.id).all()
206
+ assert first_result.id == all_results[0].id
207
+
208
+ # Test that one() and one_or_none() return same result when exactly one exists
209
+ one_result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one()
210
+ one_or_none_result = self.client.query(QueryTestUser).filter(QueryTestUser.id == 1).one_or_none()
211
+ assert one_result.id == one_or_none_result.id
212
+
213
+ def test_error_messages(self):
214
+ """Test that error messages are informative"""
215
+ # Test NoResultFound message
216
+ with pytest.raises(NoResultFound, match="No row was found for one\\(\\)"):
217
+ self.client.query(QueryTestUser).filter(QueryTestUser.id == 999).one()
218
+
219
+ # Test MultipleResultsFound message
220
+ with pytest.raises(MultipleResultsFound, match="Multiple rows were found for one_or_none\\(\\)"):
221
+ self.client.query(QueryTestUser).filter(QueryTestUser.age > 20).one_or_none()
222
+
223
+
224
+ if __name__ == '__main__':
225
+ pytest.main([__file__])