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
matrixone/version.py
ADDED
@@ -0,0 +1,580 @@
|
|
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
|
+
MatrixOne Version Management Module
|
17
|
+
|
18
|
+
Provides version comparison, compatibility checking, and version-aware feature management
|
19
|
+
for MatrixOne Python SDK.
|
20
|
+
|
21
|
+
Features:
|
22
|
+
1. Version parsing and comparison (e.g., 3.0.1, 3.0.2 > 3.0.1)
|
23
|
+
2. Backend version compatibility checking
|
24
|
+
3. Feature availability checking based on version
|
25
|
+
4. Version-aware error messages and suggestions
|
26
|
+
5. Decorators for version checking on methods
|
27
|
+
"""
|
28
|
+
|
29
|
+
import functools
|
30
|
+
import re
|
31
|
+
from dataclasses import dataclass
|
32
|
+
from enum import Enum
|
33
|
+
from typing import Callable, Dict, Optional, Union
|
34
|
+
|
35
|
+
from .exceptions import MatrixOneError
|
36
|
+
|
37
|
+
|
38
|
+
class VersionComparison(Enum):
|
39
|
+
"""Version comparison results"""
|
40
|
+
|
41
|
+
LESS = -1
|
42
|
+
EQUAL = 0
|
43
|
+
GREATER = 1
|
44
|
+
|
45
|
+
|
46
|
+
@dataclass
|
47
|
+
class VersionInfo:
|
48
|
+
"""Version information container"""
|
49
|
+
|
50
|
+
major: int
|
51
|
+
minor: int
|
52
|
+
patch: int
|
53
|
+
|
54
|
+
def __str__(self) -> str:
|
55
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
56
|
+
|
57
|
+
def __repr__(self) -> str:
|
58
|
+
return f"VersionInfo({self.major}, {self.minor}, {self.patch})"
|
59
|
+
|
60
|
+
def __eq__(self, other) -> bool:
|
61
|
+
"""Check if two versions are equal"""
|
62
|
+
if not isinstance(other, VersionInfo):
|
63
|
+
return False
|
64
|
+
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
|
65
|
+
|
66
|
+
def __lt__(self, other) -> bool:
|
67
|
+
"""Check if this version is less than other version"""
|
68
|
+
if not isinstance(other, VersionInfo):
|
69
|
+
return NotImplemented
|
70
|
+
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
71
|
+
|
72
|
+
def __le__(self, other) -> bool:
|
73
|
+
"""Check if this version is less than or equal to other version"""
|
74
|
+
if not isinstance(other, VersionInfo):
|
75
|
+
return NotImplemented
|
76
|
+
return (self.major, self.minor, self.patch) <= (other.major, other.minor, other.patch)
|
77
|
+
|
78
|
+
def __gt__(self, other) -> bool:
|
79
|
+
"""Check if this version is greater than other version"""
|
80
|
+
if not isinstance(other, VersionInfo):
|
81
|
+
return NotImplemented
|
82
|
+
return (self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
|
83
|
+
|
84
|
+
def __ge__(self, other) -> bool:
|
85
|
+
"""Check if this version is greater than or equal to other version"""
|
86
|
+
if not isinstance(other, VersionInfo):
|
87
|
+
return NotImplemented
|
88
|
+
return (self.major, self.minor, self.patch) >= (other.major, other.minor, other.patch)
|
89
|
+
|
90
|
+
|
91
|
+
@dataclass
|
92
|
+
class FeatureRequirement:
|
93
|
+
"""Feature version requirement"""
|
94
|
+
|
95
|
+
feature_name: str
|
96
|
+
min_version: Optional[VersionInfo] = None
|
97
|
+
max_version: Optional[VersionInfo] = None
|
98
|
+
description: Optional[str] = None
|
99
|
+
alternative: Optional[str] = None
|
100
|
+
|
101
|
+
|
102
|
+
class VersionManager:
|
103
|
+
"""
|
104
|
+
MatrixOne Version Manager
|
105
|
+
|
106
|
+
Handles version parsing, comparison, and compatibility checking.
|
107
|
+
Supports semantic versioning format: major.minor.patch (e.g., 3.0.1)
|
108
|
+
"""
|
109
|
+
|
110
|
+
# Version pattern for parsing
|
111
|
+
VERSION_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
112
|
+
|
113
|
+
def __init__(self):
|
114
|
+
self._current_backend_version: Optional[VersionInfo] = None
|
115
|
+
self._feature_requirements: Dict[str, FeatureRequirement] = {}
|
116
|
+
self._version_hints: Dict[str, str] = {}
|
117
|
+
|
118
|
+
def parse_version(self, version_string: str) -> VersionInfo:
|
119
|
+
"""
|
120
|
+
Parse version string into VersionInfo object
|
121
|
+
|
122
|
+
Args:
|
123
|
+
|
124
|
+
version_string: Version string in format "major.minor.patch" (e.g., "3.0.1")
|
125
|
+
Special case: "999.0.0" represents development version (highest priority)
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
|
129
|
+
VersionInfo object
|
130
|
+
|
131
|
+
Raises:
|
132
|
+
|
133
|
+
ValueError: If version string format is invalid
|
134
|
+
"""
|
135
|
+
if not isinstance(version_string, str):
|
136
|
+
raise ValueError(f"Version string must be a string, got {type(version_string)}")
|
137
|
+
|
138
|
+
match = self.VERSION_PATTERN.match(version_string.strip())
|
139
|
+
if not match:
|
140
|
+
raise ValueError(f"Invalid version format: '{version_string}'. Expected format: major.minor.patch (e.g., 3.0.1)")
|
141
|
+
|
142
|
+
major, minor, patch = map(int, match.groups())
|
143
|
+
|
144
|
+
# Special handling for development version
|
145
|
+
if major == 999:
|
146
|
+
# This is a development version - it has highest priority
|
147
|
+
pass
|
148
|
+
|
149
|
+
return VersionInfo(major, minor, patch)
|
150
|
+
|
151
|
+
def compare_versions(self, version1: Union[str, VersionInfo], version2: Union[str, VersionInfo]) -> VersionComparison:
|
152
|
+
"""
|
153
|
+
Compare two versions
|
154
|
+
|
155
|
+
Args:
|
156
|
+
|
157
|
+
version1: First version (string or VersionInfo)
|
158
|
+
version2: Second version (string or VersionInfo)
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
|
162
|
+
VersionComparison result
|
163
|
+
|
164
|
+
Examples:
|
165
|
+
|
166
|
+
compare_versions("3.0.2", "3.0.1") -> VersionComparison.GREATER
|
167
|
+
compare_versions("2.1.19", "3.0.9") -> VersionComparison.LESS
|
168
|
+
compare_versions("3.0.1", "3.0.1") -> VersionComparison.EQUAL
|
169
|
+
"""
|
170
|
+
# Parse versions if they are strings
|
171
|
+
if isinstance(version1, str):
|
172
|
+
version1 = self.parse_version(version1)
|
173
|
+
if isinstance(version2, str):
|
174
|
+
version2 = self.parse_version(version2)
|
175
|
+
|
176
|
+
# Compare major versions
|
177
|
+
if version1.major != version2.major:
|
178
|
+
return VersionComparison.GREATER if version1.major > version2.major else VersionComparison.LESS
|
179
|
+
|
180
|
+
# Compare minor versions
|
181
|
+
if version1.minor != version2.minor:
|
182
|
+
return VersionComparison.GREATER if version1.minor > version2.minor else VersionComparison.LESS
|
183
|
+
|
184
|
+
# Compare patch versions
|
185
|
+
if version1.patch != version2.patch:
|
186
|
+
return VersionComparison.GREATER if version1.patch > version2.patch else VersionComparison.LESS
|
187
|
+
|
188
|
+
return VersionComparison.EQUAL
|
189
|
+
|
190
|
+
def is_version_compatible(
|
191
|
+
self,
|
192
|
+
required_version: Union[str, VersionInfo],
|
193
|
+
current_version: Optional[Union[str, VersionInfo]] = None,
|
194
|
+
operator: str = ">=",
|
195
|
+
) -> bool:
|
196
|
+
"""
|
197
|
+
Check if current version is compatible with required version
|
198
|
+
|
199
|
+
Args:
|
200
|
+
|
201
|
+
required_version: Required version
|
202
|
+
current_version: Current version (uses backend version if None)
|
203
|
+
operator: Comparison operator (">=", ">", "<=", "<", "==", "!=")
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
|
207
|
+
True if compatible, False otherwise
|
208
|
+
"""
|
209
|
+
if current_version is None:
|
210
|
+
current_version = self._current_backend_version
|
211
|
+
|
212
|
+
if current_version is None:
|
213
|
+
# If no backend version is set, assume compatibility for now
|
214
|
+
# In real implementation, you might want to raise an error
|
215
|
+
return True
|
216
|
+
|
217
|
+
# Parse versions if they are strings
|
218
|
+
if isinstance(required_version, str):
|
219
|
+
required_version = self.parse_version(required_version)
|
220
|
+
if isinstance(current_version, str):
|
221
|
+
current_version = self.parse_version(current_version)
|
222
|
+
|
223
|
+
comparison = self.compare_versions(current_version, required_version)
|
224
|
+
|
225
|
+
if operator == ">=":
|
226
|
+
return comparison in [VersionComparison.EQUAL, VersionComparison.GREATER]
|
227
|
+
elif operator == ">":
|
228
|
+
return comparison == VersionComparison.GREATER
|
229
|
+
elif operator == "<=":
|
230
|
+
return comparison in [VersionComparison.EQUAL, VersionComparison.LESS]
|
231
|
+
elif operator == "<":
|
232
|
+
return comparison == VersionComparison.LESS
|
233
|
+
elif operator == "==":
|
234
|
+
return comparison == VersionComparison.EQUAL
|
235
|
+
elif operator == "!=":
|
236
|
+
return comparison != VersionComparison.EQUAL
|
237
|
+
else:
|
238
|
+
raise ValueError(f"Unsupported operator: {operator}")
|
239
|
+
|
240
|
+
def set_backend_version(self, version: Union[str, VersionInfo]) -> None:
|
241
|
+
"""
|
242
|
+
Set the current backend version
|
243
|
+
|
244
|
+
Args:
|
245
|
+
|
246
|
+
version: Backend version string or VersionInfo object
|
247
|
+
"""
|
248
|
+
if isinstance(version, str):
|
249
|
+
version = self.parse_version(version)
|
250
|
+
self._current_backend_version = version
|
251
|
+
|
252
|
+
def get_backend_version(self) -> Optional[VersionInfo]:
|
253
|
+
"""Get current backend version"""
|
254
|
+
return self._current_backend_version
|
255
|
+
|
256
|
+
def is_development_version(self, version: Optional[Union[str, VersionInfo]] = None) -> bool:
|
257
|
+
"""
|
258
|
+
Check if a version is a development version
|
259
|
+
|
260
|
+
Args:
|
261
|
+
|
262
|
+
version: Version to check (uses current backend version if None)
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
|
266
|
+
True if it's a development version (999.x.x), False otherwise
|
267
|
+
"""
|
268
|
+
if version is None:
|
269
|
+
version = self._current_backend_version
|
270
|
+
|
271
|
+
if version is None:
|
272
|
+
return False
|
273
|
+
|
274
|
+
if isinstance(version, str):
|
275
|
+
version = self.parse_version(version)
|
276
|
+
|
277
|
+
return version.major == 999
|
278
|
+
|
279
|
+
def _parse_matrixone_version(self, version_string: str) -> Optional[str]:
|
280
|
+
"""
|
281
|
+
Parse MatrixOne version string to extract semantic version
|
282
|
+
|
283
|
+
Handles formats:
|
284
|
+
1. "8.0.30-MatrixOne-v" -> "999.0.0" (development version, highest)
|
285
|
+
2. "8.0.30-MatrixOne-v3.0.0" -> "3.0.0" (release version)
|
286
|
+
3. "MatrixOne 3.0.1" -> "3.0.1" (fallback format)
|
287
|
+
|
288
|
+
Args:
|
289
|
+
|
290
|
+
version_string: Raw version string from MatrixOne
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
|
294
|
+
Semantic version string or None if parsing fails
|
295
|
+
"""
|
296
|
+
import re
|
297
|
+
|
298
|
+
if not version_string:
|
299
|
+
return None
|
300
|
+
|
301
|
+
version_string = version_string.strip()
|
302
|
+
|
303
|
+
# Pattern 1: Development version "8.0.30-MatrixOne-v" (v后面为空)
|
304
|
+
dev_pattern = r"^(\d+\.\d+\.\d+)-MatrixOne-v$"
|
305
|
+
dev_match = re.search(dev_pattern, version_string)
|
306
|
+
if dev_match:
|
307
|
+
# Development version - assign highest version number
|
308
|
+
return "999.0.0"
|
309
|
+
|
310
|
+
# Pattern 2: Release version "8.0.30-MatrixOne-v3.0.0" (v后面有版本号)
|
311
|
+
release_pattern = r"^(\d+\.\d+\.\d+)-MatrixOne-v(\d+\.\d+\.\d+)$"
|
312
|
+
release_match = re.search(release_pattern, version_string)
|
313
|
+
if release_match:
|
314
|
+
# Extract the semantic version part
|
315
|
+
semantic_version = release_match.group(2)
|
316
|
+
return semantic_version
|
317
|
+
|
318
|
+
# Pattern 3: Fallback format "MatrixOne 3.0.1", "Version 2.5.0", or "3.0.1"
|
319
|
+
# Match clean version strings or strings that start with common prefixes
|
320
|
+
if (
|
321
|
+
version_string.startswith("MatrixOne ")
|
322
|
+
or version_string.startswith("Version ")
|
323
|
+
or re.match(r"^\d+\.\d+\.\d+$", version_string)
|
324
|
+
):
|
325
|
+
fallback_pattern = r"(\d+\.\d+\.\d+)"
|
326
|
+
fallback_match = re.search(fallback_pattern, version_string)
|
327
|
+
if fallback_match:
|
328
|
+
return fallback_match.group(1)
|
329
|
+
|
330
|
+
# For invalid MatrixOne formats like "8.0.30-MatrixOne" or "8.0.30-MatrixOne-v-"
|
331
|
+
# return None instead of falling back to extracting version numbers
|
332
|
+
return None
|
333
|
+
|
334
|
+
def register_feature_requirement(self, feature_requirement: FeatureRequirement) -> None:
|
335
|
+
"""
|
336
|
+
Register a feature requirement
|
337
|
+
|
338
|
+
Args:
|
339
|
+
|
340
|
+
feature_requirement: FeatureRequirement object
|
341
|
+
"""
|
342
|
+
self._feature_requirements[feature_requirement.feature_name] = feature_requirement
|
343
|
+
|
344
|
+
def is_feature_available(self, feature_name: str) -> bool:
|
345
|
+
"""
|
346
|
+
Check if a feature is available in current backend version
|
347
|
+
|
348
|
+
Args:
|
349
|
+
|
350
|
+
feature_name: Name of the feature to check
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
|
354
|
+
True if feature is available, False otherwise
|
355
|
+
"""
|
356
|
+
if feature_name not in self._feature_requirements:
|
357
|
+
# If feature is not registered, assume it's available
|
358
|
+
return True
|
359
|
+
|
360
|
+
requirement = self._feature_requirements[feature_name]
|
361
|
+
current_version = self._current_backend_version
|
362
|
+
|
363
|
+
if current_version is None:
|
364
|
+
# If no backend version is set, assume feature is available
|
365
|
+
return True
|
366
|
+
|
367
|
+
# Check minimum version requirement
|
368
|
+
if requirement.min_version and not self.is_version_compatible(requirement.min_version, current_version, ">="):
|
369
|
+
return False
|
370
|
+
|
371
|
+
# Check maximum version requirement
|
372
|
+
if requirement.max_version and not self.is_version_compatible(requirement.max_version, current_version, "<="):
|
373
|
+
return False
|
374
|
+
|
375
|
+
return True
|
376
|
+
|
377
|
+
def get_feature_info(self, feature_name: str) -> Optional[FeatureRequirement]:
|
378
|
+
"""
|
379
|
+
Get feature requirement information
|
380
|
+
|
381
|
+
Args:
|
382
|
+
|
383
|
+
feature_name: Name of the feature
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
|
387
|
+
FeatureRequirement object or None if not found
|
388
|
+
"""
|
389
|
+
return self._feature_requirements.get(feature_name)
|
390
|
+
|
391
|
+
def get_version_hint(self, feature_name: str, error_context: str = "") -> str:
|
392
|
+
"""
|
393
|
+
Get helpful hint message for version-related errors
|
394
|
+
|
395
|
+
Args:
|
396
|
+
|
397
|
+
feature_name: Name of the feature
|
398
|
+
error_context: Additional context for the error
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
|
402
|
+
Helpful hint message
|
403
|
+
"""
|
404
|
+
if feature_name not in self._feature_requirements:
|
405
|
+
return f"Feature '{feature_name}' is not registered for version checking."
|
406
|
+
|
407
|
+
requirement = self._feature_requirements[feature_name]
|
408
|
+
current_version = self._current_backend_version
|
409
|
+
|
410
|
+
if current_version is None:
|
411
|
+
return "Backend version is not set. Please set the backend version using set_backend_version()."
|
412
|
+
|
413
|
+
hints = []
|
414
|
+
|
415
|
+
if requirement.min_version and not self.is_version_compatible(requirement.min_version, current_version, ">="):
|
416
|
+
hints.append(
|
417
|
+
f"Feature '{feature_name}' requires backend version {requirement.min_version} or higher, "
|
418
|
+
f"but current version is {current_version}"
|
419
|
+
)
|
420
|
+
|
421
|
+
if requirement.max_version and not self.is_version_compatible(requirement.max_version, current_version, "<="):
|
422
|
+
hints.append(
|
423
|
+
f"Feature '{feature_name}' is not supported in backend version {requirement.max_version} or higher, "
|
424
|
+
f"but current version is {current_version}"
|
425
|
+
)
|
426
|
+
|
427
|
+
if requirement.alternative:
|
428
|
+
hints.append(f"Alternative: {requirement.alternative}")
|
429
|
+
|
430
|
+
if requirement.description:
|
431
|
+
hints.append(f"Description: {requirement.description}")
|
432
|
+
|
433
|
+
if error_context:
|
434
|
+
hints.append(f"Context: {error_context}")
|
435
|
+
|
436
|
+
return "\n".join(hints)
|
437
|
+
|
438
|
+
|
439
|
+
# Global version manager instance
|
440
|
+
_version_manager = VersionManager()
|
441
|
+
|
442
|
+
|
443
|
+
def get_version_manager() -> VersionManager:
|
444
|
+
"""Get the global version manager instance"""
|
445
|
+
return _version_manager
|
446
|
+
|
447
|
+
|
448
|
+
def requires_version(
|
449
|
+
min_version: Optional[str] = None,
|
450
|
+
max_version: Optional[str] = None,
|
451
|
+
feature_name: Optional[str] = None,
|
452
|
+
description: Optional[str] = None,
|
453
|
+
alternative: Optional[str] = None,
|
454
|
+
raise_error: bool = True,
|
455
|
+
) -> Callable:
|
456
|
+
"""
|
457
|
+
Decorator for version checking on methods
|
458
|
+
|
459
|
+
Args:
|
460
|
+
|
461
|
+
min_version: Minimum required version (e.g., "3.0.1")
|
462
|
+
max_version: Maximum supported version (e.g., "3.0.5")
|
463
|
+
feature_name: Name of the feature (defaults to function name)
|
464
|
+
description: Description of the feature
|
465
|
+
alternative: Alternative approach or workaround
|
466
|
+
raise_error: Whether to raise error if version check fails
|
467
|
+
|
468
|
+
Returns:
|
469
|
+
|
470
|
+
Decorated function
|
471
|
+
"""
|
472
|
+
|
473
|
+
def decorator(func: Callable) -> Callable:
|
474
|
+
@functools.wraps(func)
|
475
|
+
def wrapper(*args, **kwargs):
|
476
|
+
# Get feature name
|
477
|
+
feature = feature_name or func.__name__
|
478
|
+
|
479
|
+
# Register feature requirement if not already registered
|
480
|
+
if feature not in _version_manager._feature_requirements:
|
481
|
+
min_ver = _version_manager.parse_version(min_version) if min_version else None
|
482
|
+
max_ver = _version_manager.parse_version(max_version) if max_version else None
|
483
|
+
|
484
|
+
requirement = FeatureRequirement(
|
485
|
+
feature_name=feature,
|
486
|
+
min_version=min_ver,
|
487
|
+
max_version=max_ver,
|
488
|
+
description=description,
|
489
|
+
alternative=alternative,
|
490
|
+
)
|
491
|
+
_version_manager.register_feature_requirement(requirement)
|
492
|
+
|
493
|
+
# Check if feature is available
|
494
|
+
if not _version_manager.is_feature_available(feature):
|
495
|
+
if raise_error:
|
496
|
+
hint = _version_manager.get_version_hint(feature, f"Method: {func.__name__}")
|
497
|
+
raise VersionError(f"Feature '{feature}' is not available in current backend version.\n{hint}")
|
498
|
+
else:
|
499
|
+
# Log warning and return None or default value
|
500
|
+
print(f"Warning: Feature '{feature}' is not available in current backend version")
|
501
|
+
return None
|
502
|
+
|
503
|
+
return func(*args, **kwargs)
|
504
|
+
|
505
|
+
# Add metadata to the function
|
506
|
+
wrapper._version_requirement = {
|
507
|
+
"min_version": min_version,
|
508
|
+
"max_version": max_version,
|
509
|
+
"feature_name": feature_name,
|
510
|
+
"description": description,
|
511
|
+
"alternative": alternative,
|
512
|
+
}
|
513
|
+
|
514
|
+
return wrapper
|
515
|
+
|
516
|
+
return decorator
|
517
|
+
|
518
|
+
|
519
|
+
class VersionError(MatrixOneError):
|
520
|
+
"""Raised when version compatibility check fails"""
|
521
|
+
|
522
|
+
pass
|
523
|
+
|
524
|
+
|
525
|
+
# Initialize common feature requirements
|
526
|
+
def _initialize_default_features():
|
527
|
+
"""Initialize default feature requirements for common MatrixOne features"""
|
528
|
+
|
529
|
+
# Snapshot features
|
530
|
+
_version_manager.register_feature_requirement(
|
531
|
+
FeatureRequirement(
|
532
|
+
feature_name="snapshot_cluster_level",
|
533
|
+
min_version=_version_manager.parse_version("1.0.0"),
|
534
|
+
description="Cluster-level snapshot functionality",
|
535
|
+
alternative="Use database-level snapshots instead",
|
536
|
+
)
|
537
|
+
)
|
538
|
+
|
539
|
+
_version_manager.register_feature_requirement(
|
540
|
+
FeatureRequirement(
|
541
|
+
feature_name="snapshot_account_level",
|
542
|
+
min_version=_version_manager.parse_version("1.0.0"),
|
543
|
+
description="Account-level snapshot functionality",
|
544
|
+
alternative="Use database-level snapshots instead",
|
545
|
+
)
|
546
|
+
)
|
547
|
+
|
548
|
+
# PITR features
|
549
|
+
_version_manager.register_feature_requirement(
|
550
|
+
FeatureRequirement(
|
551
|
+
feature_name="pitr_point_in_time_recovery",
|
552
|
+
min_version=_version_manager.parse_version("1.0.0"),
|
553
|
+
description="Point-in-time recovery functionality",
|
554
|
+
alternative="Use snapshot restore instead",
|
555
|
+
)
|
556
|
+
)
|
557
|
+
|
558
|
+
# Pub/Sub features
|
559
|
+
_version_manager.register_feature_requirement(
|
560
|
+
FeatureRequirement(
|
561
|
+
feature_name="pubsub_publications",
|
562
|
+
min_version=_version_manager.parse_version("1.0.0"),
|
563
|
+
description="Publication and subscription functionality",
|
564
|
+
alternative="Use direct table queries instead",
|
565
|
+
)
|
566
|
+
)
|
567
|
+
|
568
|
+
# Account management features
|
569
|
+
_version_manager.register_feature_requirement(
|
570
|
+
FeatureRequirement(
|
571
|
+
feature_name="account_management",
|
572
|
+
min_version=_version_manager.parse_version("1.0.0"),
|
573
|
+
description="Account and user management functionality",
|
574
|
+
alternative="Use SQL DDL statements directly",
|
575
|
+
)
|
576
|
+
)
|
577
|
+
|
578
|
+
|
579
|
+
# Initialize default features
|
580
|
+
_initialize_default_features()
|