surreal-orm-lite 0.2.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.
- surreal_orm_lite/__init__.py +24 -0
- surreal_orm_lite/connection_manager.py +313 -0
- surreal_orm_lite/constants.py +20 -0
- surreal_orm_lite/enum.py +12 -0
- surreal_orm_lite/exceptions.py +54 -0
- surreal_orm_lite/model_base.py +212 -0
- surreal_orm_lite/py.typed +0 -0
- surreal_orm_lite/query_set.py +503 -0
- surreal_orm_lite/utils.py +6 -0
- surreal_orm_lite-0.2.0.dist-info/METADATA +283 -0
- surreal_orm_lite-0.2.0.dist-info/RECORD +13 -0
- surreal_orm_lite-0.2.0.dist-info/WHEEL +4 -0
- surreal_orm_lite-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .connection_manager import SurrealDBConnectionManager
|
|
2
|
+
from .enum import OrderBy
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
SurrealDbConnectionError,
|
|
5
|
+
SurrealDbError,
|
|
6
|
+
SurrealDbNotFoundError,
|
|
7
|
+
SurrealDbValidationError,
|
|
8
|
+
SurrealORMError,
|
|
9
|
+
)
|
|
10
|
+
from .model_base import BaseSurrealModel, SurrealConfigDict
|
|
11
|
+
from .query_set import QuerySet
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SurrealDBConnectionManager",
|
|
15
|
+
"BaseSurrealModel",
|
|
16
|
+
"QuerySet",
|
|
17
|
+
"OrderBy",
|
|
18
|
+
"SurrealConfigDict",
|
|
19
|
+
"SurrealORMError",
|
|
20
|
+
"SurrealDbError",
|
|
21
|
+
"SurrealDbConnectionError",
|
|
22
|
+
"SurrealDbValidationError",
|
|
23
|
+
"SurrealDbNotFoundError",
|
|
24
|
+
]
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from surrealdb import AsyncSurreal
|
|
6
|
+
|
|
7
|
+
from .exceptions import SurrealDbConnectionError
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SurrealDBConnectionManager:
|
|
13
|
+
__url: str | None = None
|
|
14
|
+
__user: str | None = None
|
|
15
|
+
__password: str | None = None
|
|
16
|
+
__namespace: str | None = None
|
|
17
|
+
__database: str | None = None
|
|
18
|
+
__client: Any = None
|
|
19
|
+
|
|
20
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
21
|
+
await SurrealDBConnectionManager.close_connection()
|
|
22
|
+
|
|
23
|
+
async def __aenter__(self) -> Any:
|
|
24
|
+
return await SurrealDBConnectionManager.get_client()
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def set_connection(cls, url: str, user: str, password: str, namespace: str, database: str) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Set the connection kwargs for the SurrealDB instance.
|
|
30
|
+
|
|
31
|
+
:param kwargs: The connection kwargs for the SurrealDB instance.
|
|
32
|
+
"""
|
|
33
|
+
cls.__url = url
|
|
34
|
+
cls.__user = user
|
|
35
|
+
cls.__password = password
|
|
36
|
+
cls.__namespace = namespace
|
|
37
|
+
cls.__database = database
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
async def unset_connection(cls) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Set the connection kwargs for the SurrealDB instance.
|
|
43
|
+
|
|
44
|
+
:param kwargs: The connection kwargs for the SurrealDB instance.
|
|
45
|
+
"""
|
|
46
|
+
cls.__url = None
|
|
47
|
+
cls.__user = None
|
|
48
|
+
cls.__password = None
|
|
49
|
+
cls.__namespace = None
|
|
50
|
+
cls.__database = None
|
|
51
|
+
await cls.close_connection()
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def is_connection_set(cls) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Check if the connection kwargs are set.
|
|
57
|
+
|
|
58
|
+
:return: True if the connection kwargs are set, False otherwise.
|
|
59
|
+
"""
|
|
60
|
+
return all([cls.__url, cls.__user, cls.__password, cls.__namespace, cls.__database])
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
async def get_client(cls) -> Any:
|
|
64
|
+
"""
|
|
65
|
+
Connect to the SurrealDB instance.
|
|
66
|
+
|
|
67
|
+
:return: The SurrealDB instance.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
if cls.__client is not None:
|
|
71
|
+
return cls.__client
|
|
72
|
+
|
|
73
|
+
if not cls.is_connection_set():
|
|
74
|
+
raise ValueError("Connection not been set.")
|
|
75
|
+
|
|
76
|
+
# After is_connection_set(), these are guaranteed to be non-None
|
|
77
|
+
assert cls.__url is not None
|
|
78
|
+
assert cls.__namespace is not None
|
|
79
|
+
assert cls.__database is not None
|
|
80
|
+
assert cls.__user is not None
|
|
81
|
+
assert cls.__password is not None
|
|
82
|
+
|
|
83
|
+
# Établir la connexion
|
|
84
|
+
try:
|
|
85
|
+
url = cls.__url
|
|
86
|
+
_client = AsyncSurreal(url)
|
|
87
|
+
|
|
88
|
+
# WebSocket connections require explicit connect()
|
|
89
|
+
if url.startswith(("ws://", "wss://")):
|
|
90
|
+
await _client.connect(url)
|
|
91
|
+
|
|
92
|
+
await _client.use(cls.__namespace, cls.__database)
|
|
93
|
+
await _client.signin({"username": cls.__user, "password": cls.__password})
|
|
94
|
+
|
|
95
|
+
cls.__client = _client
|
|
96
|
+
return cls.__client
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.warning(f"Can't get connection: {e}")
|
|
99
|
+
if cls.__client is not None: # pragma: no cover
|
|
100
|
+
with contextlib.suppress(NotImplementedError):
|
|
101
|
+
await cls.__client.close()
|
|
102
|
+
cls.__client = None
|
|
103
|
+
raise SurrealDbConnectionError("Can't connect to the database.") from None
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
async def close_connection(cls) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Close the connection to the SurrealDB instance.
|
|
109
|
+
"""
|
|
110
|
+
# Fermer la connexion
|
|
111
|
+
|
|
112
|
+
if cls.__client is None:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
with contextlib.suppress(NotImplementedError):
|
|
116
|
+
await cls.__client.close()
|
|
117
|
+
cls.__client = None
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
async def reconnect(cls) -> Any:
|
|
121
|
+
"""
|
|
122
|
+
Reconnect to the SurrealDB instance.
|
|
123
|
+
"""
|
|
124
|
+
# Fermer la connexion
|
|
125
|
+
await cls.close_connection()
|
|
126
|
+
# Établir la connexion
|
|
127
|
+
return await cls.get_client()
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
async def validate_connection(cls) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Validate the connection to the SurrealDB instance.
|
|
133
|
+
|
|
134
|
+
:return: True if the connection is valid, False otherwise.
|
|
135
|
+
"""
|
|
136
|
+
# Valider la connexion
|
|
137
|
+
try:
|
|
138
|
+
await cls.reconnect()
|
|
139
|
+
return True
|
|
140
|
+
except SurrealDbConnectionError:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def get_connection_string(cls) -> str | None:
|
|
145
|
+
"""
|
|
146
|
+
Get the connection string for the SurrealDB instance.
|
|
147
|
+
|
|
148
|
+
:return: The connection string for the SurrealDB instance.
|
|
149
|
+
"""
|
|
150
|
+
return cls.__url
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def get_connection_kwargs(cls) -> dict[str, str | None]:
|
|
154
|
+
"""
|
|
155
|
+
Get the connection kwargs for the SurrealDB instance.
|
|
156
|
+
|
|
157
|
+
:return: The connection kwargs for the SurrealDB instance.
|
|
158
|
+
"""
|
|
159
|
+
return {
|
|
160
|
+
"url": cls.__url,
|
|
161
|
+
"user": cls.__user,
|
|
162
|
+
"namespace": cls.__namespace,
|
|
163
|
+
"database": cls.__database,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
async def set_url(cls, url: str, reconnect: bool = False) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
Set the URL for the SurrealDB instance.
|
|
170
|
+
|
|
171
|
+
:param url: The URL of the SurrealDB instance.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
if not cls.is_connection_set():
|
|
175
|
+
raise ValueError("You can't change the URL when the others setting are not already set.")
|
|
176
|
+
|
|
177
|
+
cls.__url = url
|
|
178
|
+
|
|
179
|
+
if reconnect and not await cls.validate_connection(): # pragma: no cover
|
|
180
|
+
cls.__url = None
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
async def set_user(cls, user: str, reconnect: bool = False) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Set the username for authentication.
|
|
189
|
+
|
|
190
|
+
:param user: The username for authentication.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
if not cls.is_connection_set():
|
|
194
|
+
raise ValueError("You can't change the User when the others setting are not already set.")
|
|
195
|
+
|
|
196
|
+
cls.__user = user
|
|
197
|
+
|
|
198
|
+
if reconnect and not await cls.validate_connection(): # pragma: no cover
|
|
199
|
+
cls.__user = None
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
async def set_password(cls, password: str, reconnect: bool = False) -> bool:
|
|
206
|
+
"""
|
|
207
|
+
Set the password for authentication.
|
|
208
|
+
|
|
209
|
+
:param password: The password for authentication.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
if not cls.is_connection_set():
|
|
213
|
+
raise ValueError("You can't change the password when the others setting are not already set.")
|
|
214
|
+
|
|
215
|
+
cls.__password = password
|
|
216
|
+
|
|
217
|
+
if reconnect and not await cls.validate_connection(): # pragma: no cover
|
|
218
|
+
cls.__password = None
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
async def set_namespace(cls, namespace: str, reconnect: bool = False) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Set the namespace to use.
|
|
227
|
+
|
|
228
|
+
:param namespace: The namespace to use.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
if not cls.is_connection_set():
|
|
232
|
+
raise ValueError("You can't change the namespace when the others setting are not already set.")
|
|
233
|
+
|
|
234
|
+
cls.__namespace = namespace
|
|
235
|
+
|
|
236
|
+
if reconnect and not await cls.validate_connection(): # pragma: no cover
|
|
237
|
+
cls.__namespace = None
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
async def set_database(cls, database: str, reconnect: bool = False) -> bool:
|
|
244
|
+
"""
|
|
245
|
+
Set the database to use.
|
|
246
|
+
|
|
247
|
+
:param database: The database to use.
|
|
248
|
+
"""
|
|
249
|
+
if not cls.is_connection_set():
|
|
250
|
+
raise ValueError("You can't change the database when the others setting are not already set.")
|
|
251
|
+
|
|
252
|
+
cls.__database = database
|
|
253
|
+
|
|
254
|
+
if reconnect and not await cls.validate_connection(): # pragma: no cover
|
|
255
|
+
cls.__database = None
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def get_url(cls) -> str | None:
|
|
262
|
+
"""
|
|
263
|
+
Get the URL of the SurrealDB instance.
|
|
264
|
+
|
|
265
|
+
:return: The URL of the SurrealDB instance.
|
|
266
|
+
"""
|
|
267
|
+
return cls.__url
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def get_user(cls) -> str | None:
|
|
271
|
+
"""
|
|
272
|
+
Get the username for authentication.
|
|
273
|
+
|
|
274
|
+
:return: The username for authentication.
|
|
275
|
+
"""
|
|
276
|
+
return cls.__user
|
|
277
|
+
|
|
278
|
+
@classmethod
|
|
279
|
+
def get_namespace(cls) -> str | None:
|
|
280
|
+
"""
|
|
281
|
+
Get the namespace to use.
|
|
282
|
+
|
|
283
|
+
:return: The namespace to use.
|
|
284
|
+
"""
|
|
285
|
+
return cls.__namespace
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def get_database(cls) -> str | None:
|
|
289
|
+
"""
|
|
290
|
+
Get the database to use.
|
|
291
|
+
|
|
292
|
+
:return: The database to use.
|
|
293
|
+
"""
|
|
294
|
+
return cls.__database
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def is_password_set(cls) -> bool:
|
|
298
|
+
"""
|
|
299
|
+
Get the database to use.
|
|
300
|
+
|
|
301
|
+
:return: The database to use.
|
|
302
|
+
"""
|
|
303
|
+
return cls.__password is not None
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def is_connected(cls) -> bool:
|
|
307
|
+
"""
|
|
308
|
+
Check if the connection to the SurrealDB instance is established.
|
|
309
|
+
|
|
310
|
+
:return: True if the connection is established, False otherwise.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
return cls.__client is not None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LOOKUP_OPERATORS = {
|
|
2
|
+
"exact": "=",
|
|
3
|
+
"gt": ">",
|
|
4
|
+
"gte": ">=",
|
|
5
|
+
"lt": "<",
|
|
6
|
+
"lte": "<=",
|
|
7
|
+
"in": "IN",
|
|
8
|
+
"like": "LIKE",
|
|
9
|
+
"ilike": "ILIKE",
|
|
10
|
+
"contains": "CONTAINS",
|
|
11
|
+
"icontains": "CONTAINS",
|
|
12
|
+
"startswith": "STARTSWITH",
|
|
13
|
+
"istartswith": "STARTSWITH",
|
|
14
|
+
"endswith": "ENDSWITH",
|
|
15
|
+
"iendswith": "ENDSWITH",
|
|
16
|
+
"match": "MATCH",
|
|
17
|
+
"regex": "REGEX",
|
|
18
|
+
"iregex": "REGEX",
|
|
19
|
+
"isnull": "IS",
|
|
20
|
+
}
|
surreal_orm_lite/enum.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for SurrealDB ORM.
|
|
3
|
+
|
|
4
|
+
These exceptions provide a consistent error handling interface
|
|
5
|
+
for the ORM, independent of the underlying SDK error types.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SurrealORMError(Exception):
|
|
10
|
+
"""Base exception for all SurrealDB ORM errors."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SurrealDbError(SurrealORMError):
|
|
16
|
+
"""
|
|
17
|
+
General database error.
|
|
18
|
+
|
|
19
|
+
Raised when a database operation fails for reasons
|
|
20
|
+
like invalid data, constraint violations, etc.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SurrealDbConnectionError(SurrealORMError):
|
|
27
|
+
"""
|
|
28
|
+
Connection error.
|
|
29
|
+
|
|
30
|
+
Raised when the connection to SurrealDB cannot be established
|
|
31
|
+
or when an existing connection is lost.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SurrealDbValidationError(SurrealORMError):
|
|
38
|
+
"""
|
|
39
|
+
Validation error.
|
|
40
|
+
|
|
41
|
+
Raised when data validation fails before sending to the database.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SurrealDbNotFoundError(SurrealORMError):
|
|
48
|
+
"""
|
|
49
|
+
Not found error.
|
|
50
|
+
|
|
51
|
+
Raised when a requested record or resource is not found.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
pass
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Self
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
5
|
+
from surrealdb import RecordID
|
|
6
|
+
|
|
7
|
+
from .connection_manager import SurrealDBConnectionManager
|
|
8
|
+
from .exceptions import SurrealDbError
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SurrealConfigDict(ConfigDict):
|
|
14
|
+
"""
|
|
15
|
+
SurrealConfigDict is a configuration dictionary for SurrealDB models.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
primary_key (str | None): The primary key field name for the model.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
primary_key: str | None
|
|
22
|
+
" The primary key field name for the model. "
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseSurrealModel(BaseModel):
|
|
26
|
+
"""
|
|
27
|
+
Base class for models interacting with SurrealDB.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_table_name(cls) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Get the table name for the model.
|
|
34
|
+
"""
|
|
35
|
+
return cls.__name__
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_index_primary_key(cls) -> str | None:
|
|
39
|
+
"""
|
|
40
|
+
Get the primary key field name for the model.
|
|
41
|
+
"""
|
|
42
|
+
if hasattr(cls, "model_config"): # pragma: no cover
|
|
43
|
+
primary_key = cls.model_config.get("primary_key", None)
|
|
44
|
+
if isinstance(primary_key, str):
|
|
45
|
+
return primary_key
|
|
46
|
+
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
def get_id(self) -> None | str | RecordID:
|
|
50
|
+
"""
|
|
51
|
+
Get the ID of the model instance.
|
|
52
|
+
"""
|
|
53
|
+
if hasattr(self, "id"):
|
|
54
|
+
id_value = self.id
|
|
55
|
+
return str(id_value) if id_value is not None else None
|
|
56
|
+
|
|
57
|
+
if hasattr(self, "model_config"):
|
|
58
|
+
primary_key = self.model_config.get("primary_key", None)
|
|
59
|
+
if isinstance(primary_key, str) and hasattr(self, primary_key):
|
|
60
|
+
primary_key_value = getattr(self, primary_key)
|
|
61
|
+
return str(primary_key_value) if primary_key_value is not None else None
|
|
62
|
+
|
|
63
|
+
return None # pragma: no cover
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_db(cls, record: dict | list) -> Self | list[Self]:
|
|
67
|
+
"""
|
|
68
|
+
Create an instance from a SurrealDB record.
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(record, list):
|
|
71
|
+
return [cls.from_db(rs) for rs in record] # type: ignore
|
|
72
|
+
|
|
73
|
+
return cls(**record)
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="before")
|
|
76
|
+
@classmethod
|
|
77
|
+
def set_data(cls, data: Any) -> Any:
|
|
78
|
+
"""
|
|
79
|
+
Set the ID of the model instance.
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(data, dict): # pragma: no cover
|
|
82
|
+
if "id" in data and isinstance(data["id"], RecordID):
|
|
83
|
+
data["id"] = str(data["id"]).split(":")[1]
|
|
84
|
+
return data
|
|
85
|
+
|
|
86
|
+
async def refresh(self) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Refresh the model instance from the database.
|
|
89
|
+
"""
|
|
90
|
+
if not self.get_id():
|
|
91
|
+
raise SurrealDbError("Can't refresh data, not recorded yet.") # pragma: no cover
|
|
92
|
+
|
|
93
|
+
client = await SurrealDBConnectionManager.get_client()
|
|
94
|
+
record = await client.select(f"{self.get_table_name()}:{self.get_id()}")
|
|
95
|
+
|
|
96
|
+
if record is None:
|
|
97
|
+
raise SurrealDbError("Can't refresh data, no record found.") # pragma: no cover
|
|
98
|
+
|
|
99
|
+
self.from_db(record)
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
async def save(self) -> Self:
|
|
103
|
+
"""
|
|
104
|
+
Save the model instance to the database.
|
|
105
|
+
"""
|
|
106
|
+
client = await SurrealDBConnectionManager.get_client()
|
|
107
|
+
data = self.model_dump(exclude={"id"})
|
|
108
|
+
id = self.get_id()
|
|
109
|
+
table = self.get_table_name()
|
|
110
|
+
|
|
111
|
+
if id is not None:
|
|
112
|
+
# Escape special characters in ID
|
|
113
|
+
escaped_id = f"`{id}`" if any(c in str(id) for c in "@#$%^&*()") else id
|
|
114
|
+
thing = f"{table}:{escaped_id}"
|
|
115
|
+
result = await client.create(thing, data)
|
|
116
|
+
# SDK 1.0.8 returns error message as string instead of raising exception
|
|
117
|
+
if isinstance(result, str) and "already exists" in result:
|
|
118
|
+
raise SurrealDbError(f"There was a problem with the database: {result}")
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
# Auto-generate the ID
|
|
122
|
+
record = await client.create(table, data) # pragma: no cover
|
|
123
|
+
|
|
124
|
+
# SDK 1.0.8 returns error message as string
|
|
125
|
+
if isinstance(record, str):
|
|
126
|
+
raise SurrealDbError(f"Can't save data: {record}") # pragma: no cover
|
|
127
|
+
|
|
128
|
+
if isinstance(record, list):
|
|
129
|
+
raise SurrealDbError("Can't save data, multiple records returned.") # pragma: no cover
|
|
130
|
+
|
|
131
|
+
if record is None:
|
|
132
|
+
raise SurrealDbError("Can't save data, no record returned.") # pragma: no cover
|
|
133
|
+
|
|
134
|
+
obj = self.from_db(record)
|
|
135
|
+
if isinstance(obj, type(self)):
|
|
136
|
+
self = obj
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
raise SurrealDbError("Can't save data, no record returned.") # pragma: no cover
|
|
140
|
+
|
|
141
|
+
async def update(self) -> Any:
|
|
142
|
+
"""
|
|
143
|
+
Update the model instance to the database.
|
|
144
|
+
"""
|
|
145
|
+
client = await SurrealDBConnectionManager.get_client()
|
|
146
|
+
|
|
147
|
+
data = self.model_dump(exclude={"id"})
|
|
148
|
+
id = self.get_id()
|
|
149
|
+
if id is not None:
|
|
150
|
+
thing = f"{self.__class__.__name__}:{id}"
|
|
151
|
+
test = await client.update(thing, data)
|
|
152
|
+
return test
|
|
153
|
+
raise SurrealDbError("Can't update data, no id found.")
|
|
154
|
+
|
|
155
|
+
async def merge(self, **data: Any) -> Any:
|
|
156
|
+
"""
|
|
157
|
+
Update the model instance to the database.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
client = await SurrealDBConnectionManager.get_client()
|
|
161
|
+
data_set = dict(data.items())
|
|
162
|
+
|
|
163
|
+
id = self.get_id()
|
|
164
|
+
if id:
|
|
165
|
+
thing = f"{self.get_table_name()}:{id}"
|
|
166
|
+
|
|
167
|
+
await client.merge(thing, data_set)
|
|
168
|
+
await self.refresh()
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
raise SurrealDbError(f"No Id for the data to merge: {data}")
|
|
172
|
+
|
|
173
|
+
async def delete(self) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Delete the model instance from the database.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
client = await SurrealDBConnectionManager.get_client()
|
|
179
|
+
|
|
180
|
+
id = self.get_id()
|
|
181
|
+
|
|
182
|
+
thing = f"{self.get_table_name()}:{id}"
|
|
183
|
+
|
|
184
|
+
deleted = await client.delete(thing)
|
|
185
|
+
|
|
186
|
+
if not deleted:
|
|
187
|
+
raise SurrealDbError(f"Can't delete Record id -> '{id}' not found!")
|
|
188
|
+
|
|
189
|
+
logger.info(f"Record deleted -> {deleted}.")
|
|
190
|
+
del self
|
|
191
|
+
|
|
192
|
+
@model_validator(mode="after")
|
|
193
|
+
def check_config(self) -> Self:
|
|
194
|
+
"""
|
|
195
|
+
Check the model configuration.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
if not self.get_index_primary_key() and not hasattr(self, "id"):
|
|
199
|
+
raise SurrealDbError( # pragma: no cover
|
|
200
|
+
"Can't create model, the model need either 'id' field or primirary_key in 'model_config'."
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def objects(cls) -> Any:
|
|
207
|
+
"""
|
|
208
|
+
Return a QuerySet for the model class.
|
|
209
|
+
"""
|
|
210
|
+
from .query_set import QuerySet
|
|
211
|
+
|
|
212
|
+
return QuerySet(cls)
|
|
File without changes
|