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,374 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
# Copyright 2021 - 2022 Matrix Origin
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
"""
|
18
|
+
Online Test: filter(logical_in(...)) syntax for enhanced IN functionality
|
19
|
+
|
20
|
+
This test verifies that the filter(logical_in(...)) syntax works correctly with actual
|
21
|
+
database connections, including FulltextFilter objects, lists, and SQLAlchemy expressions.
|
22
|
+
"""
|
23
|
+
|
24
|
+
import pytest
|
25
|
+
import sys
|
26
|
+
import os
|
27
|
+
from datetime import datetime
|
28
|
+
|
29
|
+
# Add the current directory to Python path
|
30
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
31
|
+
|
32
|
+
from matrixone import Client
|
33
|
+
from sqlalchemy import Column, Integer, String, DateTime, func
|
34
|
+
from matrixone.orm import declarative_base, logical_in
|
35
|
+
from matrixone.sqlalchemy_ext.fulltext_search import boolean_match
|
36
|
+
|
37
|
+
try:
|
38
|
+
from .test_config import online_config
|
39
|
+
except ImportError:
|
40
|
+
# Fallback for when running as standalone script
|
41
|
+
import test_config
|
42
|
+
|
43
|
+
online_config = test_config.online_config
|
44
|
+
|
45
|
+
# Create SQLAlchemy models
|
46
|
+
Base = declarative_base()
|
47
|
+
|
48
|
+
|
49
|
+
class User(Base):
|
50
|
+
__tablename__ = 'test_users_logical_in'
|
51
|
+
id = Column(Integer, primary_key=True)
|
52
|
+
name = Column(String(50))
|
53
|
+
age = Column(Integer)
|
54
|
+
city = Column(String(50))
|
55
|
+
department = Column(String(50))
|
56
|
+
salary = Column(Integer)
|
57
|
+
created_at = Column(DateTime)
|
58
|
+
|
59
|
+
|
60
|
+
class Article(Base):
|
61
|
+
__tablename__ = 'test_articles_logical_in'
|
62
|
+
id = Column(Integer, primary_key=True)
|
63
|
+
title = Column(String(100))
|
64
|
+
content = Column(String(500))
|
65
|
+
author = Column(String(50))
|
66
|
+
created_at = Column(DateTime)
|
67
|
+
|
68
|
+
|
69
|
+
class TestFilterLogicalInOnline:
|
70
|
+
"""Online test class for filter(logical_in(...)) syntax functionality"""
|
71
|
+
|
72
|
+
@pytest.fixture(autouse=True)
|
73
|
+
def setup_and_cleanup(self):
|
74
|
+
"""Set up test data and clean up after tests"""
|
75
|
+
# Setup - use real database connection
|
76
|
+
host, port, user, password, database = online_config.get_connection_params()
|
77
|
+
self.client = Client()
|
78
|
+
self.client.connect(host=host, port=port, user=user, password=password, database=database)
|
79
|
+
|
80
|
+
# Create tables
|
81
|
+
self.client.create_table(
|
82
|
+
"test_users_logical_in",
|
83
|
+
{
|
84
|
+
"id": "int",
|
85
|
+
"name": "varchar(50)",
|
86
|
+
"age": "int",
|
87
|
+
"city": "varchar(50)",
|
88
|
+
"department": "varchar(50)",
|
89
|
+
"salary": "int",
|
90
|
+
"created_at": "datetime",
|
91
|
+
},
|
92
|
+
primary_key="id",
|
93
|
+
)
|
94
|
+
self.client.create_table(
|
95
|
+
"test_articles_logical_in",
|
96
|
+
{
|
97
|
+
"id": "int",
|
98
|
+
"title": "varchar(100)",
|
99
|
+
"content": "varchar(500)",
|
100
|
+
"author": "varchar(50)",
|
101
|
+
"created_at": "datetime",
|
102
|
+
},
|
103
|
+
primary_key="id",
|
104
|
+
)
|
105
|
+
|
106
|
+
# Insert test data
|
107
|
+
self._insert_test_data()
|
108
|
+
|
109
|
+
yield
|
110
|
+
|
111
|
+
# Cleanup
|
112
|
+
try:
|
113
|
+
self.client.drop_table("test_users_logical_in")
|
114
|
+
self.client.drop_table("test_articles_logical_in")
|
115
|
+
except:
|
116
|
+
pass # Ignore cleanup errors
|
117
|
+
finally:
|
118
|
+
# Disconnect from database
|
119
|
+
try:
|
120
|
+
self.client.close()
|
121
|
+
except:
|
122
|
+
pass
|
123
|
+
|
124
|
+
def _insert_test_data(self):
|
125
|
+
"""Insert test data for online tests"""
|
126
|
+
# Insert users
|
127
|
+
users_data = [
|
128
|
+
{"name": "张三", "age": 25, "city": "北京", "department": "Engineering", "salary": 80000},
|
129
|
+
{"name": "李四", "age": 30, "city": "上海", "department": "Sales", "salary": 70000},
|
130
|
+
{"name": "王五", "age": 35, "city": "广州", "department": "Engineering", "salary": 90000},
|
131
|
+
{"name": "赵六", "age": 28, "city": "深圳", "department": "Marketing", "salary": 75000},
|
132
|
+
{"name": "钱七", "age": 32, "city": "北京", "department": "Sales", "salary": 85000},
|
133
|
+
{"name": "孙八", "age": 27, "city": "上海", "department": "Engineering", "salary": 80000},
|
134
|
+
]
|
135
|
+
|
136
|
+
for user_data in users_data:
|
137
|
+
self.client.insert("test_users_logical_in", user_data)
|
138
|
+
|
139
|
+
# Insert articles
|
140
|
+
articles_data = [
|
141
|
+
{"title": "Python编程入门", "content": "Python是一种高级编程语言,适合初学者学习", "author": "张三"},
|
142
|
+
{"title": "Java开发指南", "content": "Java是企业级应用开发的首选语言", "author": "李四"},
|
143
|
+
{"title": "Python数据分析", "content": "使用Python进行数据分析和机器学习", "author": "王五"},
|
144
|
+
{"title": "JavaScript前端开发", "content": "JavaScript是现代前端开发的核心技术", "author": "赵六"},
|
145
|
+
{"title": "Python Web开发", "content": "使用Python和Django进行Web应用开发", "author": "张三"},
|
146
|
+
{"title": "数据库设计原理", "content": "关系型数据库的设计和优化技巧", "author": "钱七"},
|
147
|
+
]
|
148
|
+
|
149
|
+
for article_data in articles_data:
|
150
|
+
self.client.insert("test_articles_logical_in", article_data)
|
151
|
+
|
152
|
+
def test_filter_logical_in_with_list_values_online(self):
|
153
|
+
"""Test filter(logical_in(...)) with list of values - online"""
|
154
|
+
# Test with city list
|
155
|
+
query = self.client.query(User).filter(logical_in("city", ["北京", "上海"]))
|
156
|
+
results = query.all()
|
157
|
+
|
158
|
+
# Should have 4 users: 张三(北京), 李四(上海), 钱七(北京), 孙八(上海)
|
159
|
+
assert len(results) == 4
|
160
|
+
|
161
|
+
cities = [user.city for user in results]
|
162
|
+
assert "北京" in cities
|
163
|
+
assert "上海" in cities
|
164
|
+
assert "广州" not in cities
|
165
|
+
assert "深圳" not in cities
|
166
|
+
|
167
|
+
def test_filter_logical_in_with_department_list_online(self):
|
168
|
+
"""Test filter(logical_in(...)) with department list - online"""
|
169
|
+
query = self.client.query(User).filter(logical_in("department", ["Engineering", "Sales"]))
|
170
|
+
results = query.all()
|
171
|
+
|
172
|
+
# Should have 5 users: 张三(Engineering), 李四(Sales), 王五(Engineering), 钱七(Sales), 孙八(Engineering)
|
173
|
+
assert len(results) == 5
|
174
|
+
|
175
|
+
departments = [user.department for user in results]
|
176
|
+
assert "Engineering" in departments
|
177
|
+
assert "Sales" in departments
|
178
|
+
assert "Marketing" not in departments
|
179
|
+
|
180
|
+
def test_filter_logical_in_with_age_range_online(self):
|
181
|
+
"""Test filter(logical_in(...)) with age range - online"""
|
182
|
+
query = self.client.query(User).filter(logical_in("age", [25, 30, 35]))
|
183
|
+
results = query.all()
|
184
|
+
|
185
|
+
# Should have 3 users: 张三(25), 李四(30), 王五(35)
|
186
|
+
assert len(results) == 3
|
187
|
+
|
188
|
+
ages = [user.age for user in results]
|
189
|
+
assert 25 in ages
|
190
|
+
assert 30 in ages
|
191
|
+
assert 35 in ages
|
192
|
+
assert 28 not in ages
|
193
|
+
assert 32 not in ages
|
194
|
+
assert 27 not in ages
|
195
|
+
|
196
|
+
def test_filter_logical_in_with_salary_range_online(self):
|
197
|
+
"""Test filter(logical_in(...)) with salary range - online"""
|
198
|
+
query = self.client.query(User).filter(logical_in("salary", [80000, 90000]))
|
199
|
+
results = query.all()
|
200
|
+
|
201
|
+
# Should have 3 users: 张三(80000), 王五(90000), 孙八(80000)
|
202
|
+
assert len(results) == 3
|
203
|
+
|
204
|
+
salaries = [user.salary for user in results]
|
205
|
+
assert 80000 in salaries
|
206
|
+
assert 90000 in salaries
|
207
|
+
assert 70000 not in salaries
|
208
|
+
assert 75000 not in salaries
|
209
|
+
assert 85000 not in salaries
|
210
|
+
|
211
|
+
def test_filter_logical_in_with_author_list_online(self):
|
212
|
+
"""Test filter(logical_in(...)) with author list - online"""
|
213
|
+
query = self.client.query(Article).filter(logical_in("author", ["张三", "李四"]))
|
214
|
+
results = query.all()
|
215
|
+
|
216
|
+
# Should have 3 articles: 张三的2篇文章 + 李四的1篇文章
|
217
|
+
assert len(results) == 3
|
218
|
+
|
219
|
+
authors = [article.author for article in results]
|
220
|
+
assert "张三" in authors
|
221
|
+
assert "李四" in authors
|
222
|
+
assert "王五" not in authors
|
223
|
+
assert "赵六" not in authors
|
224
|
+
assert "钱七" not in authors
|
225
|
+
|
226
|
+
def test_filter_logical_in_combined_with_other_conditions_online(self):
|
227
|
+
"""Test filter(logical_in(...)) combined with other conditions - online"""
|
228
|
+
query = (
|
229
|
+
self.client.query(User)
|
230
|
+
.filter(logical_in("city", ["北京", "上海"]))
|
231
|
+
.filter(logical_in("department", ["Engineering", "Sales"]))
|
232
|
+
.filter("age > ?", 25)
|
233
|
+
)
|
234
|
+
|
235
|
+
results = query.all()
|
236
|
+
|
237
|
+
# Should return 3 users: 李四(上海, Sales, 30), 钱七(北京, Sales, 32), 孙八(上海, Engineering, 27)
|
238
|
+
assert len(results) == 3
|
239
|
+
|
240
|
+
for user in results:
|
241
|
+
assert user.city in ["北京", "上海"]
|
242
|
+
assert user.department in ["Engineering", "Sales"]
|
243
|
+
assert user.age > 25
|
244
|
+
|
245
|
+
def test_filter_logical_in_with_empty_list_online(self):
|
246
|
+
"""Test filter(logical_in(...)) with empty list - online"""
|
247
|
+
query = self.client.query(User).filter(logical_in("id", []))
|
248
|
+
results = query.all()
|
249
|
+
|
250
|
+
assert len(results) == 0 # Empty list should return no results
|
251
|
+
|
252
|
+
def test_filter_logical_in_with_single_value_online(self):
|
253
|
+
"""Test filter(logical_in(...)) with single value - online"""
|
254
|
+
query = self.client.query(User).filter(logical_in("id", 1))
|
255
|
+
results = query.all()
|
256
|
+
|
257
|
+
assert len(results) == 1
|
258
|
+
assert results[0].id == 1
|
259
|
+
assert results[0].name == "张三"
|
260
|
+
|
261
|
+
def test_filter_logical_in_with_tuple_values_online(self):
|
262
|
+
"""Test filter(logical_in(...)) with tuple values - online"""
|
263
|
+
query = self.client.query(User).filter(logical_in("id", (1, 2, 3)))
|
264
|
+
results = query.all()
|
265
|
+
|
266
|
+
assert len(results) == 3
|
267
|
+
|
268
|
+
ids = [user.id for user in results]
|
269
|
+
assert 1 in ids
|
270
|
+
assert 2 in ids
|
271
|
+
assert 3 in ids
|
272
|
+
assert 4 not in ids
|
273
|
+
assert 5 not in ids
|
274
|
+
assert 6 not in ids
|
275
|
+
|
276
|
+
def test_filter_logical_in_method_chaining_online(self):
|
277
|
+
"""Test filter(logical_in(...)) with method chaining - online"""
|
278
|
+
query = (
|
279
|
+
self.client.query(User)
|
280
|
+
.filter(logical_in("city", ["北京", "上海"]))
|
281
|
+
.filter(logical_in("department", ["Engineering", "Sales"]))
|
282
|
+
.filter("age > ?", 25)
|
283
|
+
.order_by("name")
|
284
|
+
.limit(10)
|
285
|
+
)
|
286
|
+
|
287
|
+
results = query.all()
|
288
|
+
|
289
|
+
# Should return 3 users: 李四(上海, Sales, 30), 钱七(北京, Sales, 32), 孙八(上海, Engineering, 27)
|
290
|
+
assert len(results) == 3
|
291
|
+
|
292
|
+
# Results should be ordered by name
|
293
|
+
names = [user.name for user in results]
|
294
|
+
assert names == sorted(names)
|
295
|
+
|
296
|
+
def test_filter_logical_in_with_aggregate_functions_online(self):
|
297
|
+
"""Test filter(logical_in(...)) with aggregate functions - online"""
|
298
|
+
# Test with count function
|
299
|
+
query = (
|
300
|
+
self.client.query(User)
|
301
|
+
.select(User.department, func.count(User.id))
|
302
|
+
.filter(logical_in("city", ["北京", "上海", "广州"]))
|
303
|
+
.group_by(User.department)
|
304
|
+
.having(func.count(User.id) > 1)
|
305
|
+
)
|
306
|
+
|
307
|
+
results = query.all()
|
308
|
+
|
309
|
+
# Should return departments with more than 1 user in the specified cities
|
310
|
+
assert len(results) >= 0 # May vary based on data
|
311
|
+
|
312
|
+
for result in results:
|
313
|
+
assert result.count > 1
|
314
|
+
|
315
|
+
def test_filter_logical_in_performance_online(self):
|
316
|
+
"""Test filter(logical_in(...)) performance with complex queries - online"""
|
317
|
+
query = (
|
318
|
+
self.client.query(User)
|
319
|
+
.select(User.department, func.count(User.id))
|
320
|
+
.filter(logical_in("city", ["北京", "上海", "广州", "深圳"]))
|
321
|
+
.filter(logical_in("department", ["Engineering", "Sales", "Marketing"]))
|
322
|
+
.filter("age > ?", 25)
|
323
|
+
.group_by(User.department)
|
324
|
+
.having(func.count(User.id) > 0)
|
325
|
+
.order_by(func.count(User.id).desc())
|
326
|
+
.limit(20)
|
327
|
+
)
|
328
|
+
|
329
|
+
results = query.all()
|
330
|
+
|
331
|
+
# Should return aggregated results
|
332
|
+
assert len(results) >= 0
|
333
|
+
|
334
|
+
# Results should be ordered by count descending
|
335
|
+
if len(results) > 1:
|
336
|
+
counts = [result.count for result in results]
|
337
|
+
assert counts == sorted(counts, reverse=True)
|
338
|
+
|
339
|
+
def test_filter_logical_in_with_article_search_online(self):
|
340
|
+
"""Test filter(logical_in(...)) with article search - online"""
|
341
|
+
# Search for articles by specific authors
|
342
|
+
query = self.client.query(Article).filter(logical_in("author", ["张三", "王五"]))
|
343
|
+
results = query.all()
|
344
|
+
|
345
|
+
assert len(results) == 3 # 张三的2篇文章 + 王五的1篇文章
|
346
|
+
|
347
|
+
authors = [article.author for article in results]
|
348
|
+
assert "张三" in authors
|
349
|
+
assert "王五" in authors
|
350
|
+
assert "李四" not in authors
|
351
|
+
assert "赵六" not in authors
|
352
|
+
assert "钱七" not in authors
|
353
|
+
|
354
|
+
def test_filter_logical_in_with_mixed_conditions_online(self):
|
355
|
+
"""Test filter(logical_in(...)) with mixed conditions - online"""
|
356
|
+
query = (
|
357
|
+
self.client.query(Article)
|
358
|
+
.filter(logical_in("author", ["张三", "李四", "王五"]))
|
359
|
+
.filter("title LIKE ?", "%Python%")
|
360
|
+
)
|
361
|
+
|
362
|
+
results = query.all()
|
363
|
+
|
364
|
+
# Should return Python-related articles by specified authors
|
365
|
+
assert len(results) >= 0
|
366
|
+
|
367
|
+
for article in results:
|
368
|
+
assert article.author in ["张三", "李四", "王五"]
|
369
|
+
assert "Python" in article.title
|
370
|
+
|
371
|
+
|
372
|
+
if __name__ == "__main__":
|
373
|
+
# Run tests if executed directly
|
374
|
+
pytest.main([__file__, "-v"])
|