surrealdb-orm 0.1.4__py3-none-any.whl → 0.5.1__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.
Potentially problematic release.
This version of surrealdb-orm might be problematic. Click here for more details.
- surreal_orm/__init__.py +72 -3
- surreal_orm/aggregations.py +164 -0
- surreal_orm/auth/__init__.py +15 -0
- surreal_orm/auth/access.py +167 -0
- surreal_orm/auth/mixins.py +302 -0
- surreal_orm/cli/__init__.py +15 -0
- surreal_orm/cli/commands.py +369 -0
- surreal_orm/connection_manager.py +58 -18
- surreal_orm/fields/__init__.py +36 -0
- surreal_orm/fields/encrypted.py +166 -0
- surreal_orm/fields/relation.py +465 -0
- surreal_orm/migrations/__init__.py +51 -0
- surreal_orm/migrations/executor.py +380 -0
- surreal_orm/migrations/generator.py +272 -0
- surreal_orm/migrations/introspector.py +305 -0
- surreal_orm/migrations/migration.py +188 -0
- surreal_orm/migrations/operations.py +531 -0
- surreal_orm/migrations/state.py +406 -0
- surreal_orm/model_base.py +530 -44
- surreal_orm/query_set.py +609 -33
- surreal_orm/relations.py +645 -0
- surreal_orm/surreal_function.py +95 -0
- surreal_orm/surreal_ql.py +113 -0
- surreal_orm/types.py +86 -0
- surreal_sdk/README.md +79 -0
- surreal_sdk/__init__.py +151 -0
- surreal_sdk/connection/__init__.py +17 -0
- surreal_sdk/connection/base.py +516 -0
- surreal_sdk/connection/http.py +421 -0
- surreal_sdk/connection/pool.py +244 -0
- surreal_sdk/connection/websocket.py +519 -0
- surreal_sdk/exceptions.py +71 -0
- surreal_sdk/functions.py +607 -0
- surreal_sdk/protocol/__init__.py +13 -0
- surreal_sdk/protocol/rpc.py +218 -0
- surreal_sdk/py.typed +0 -0
- surreal_sdk/pyproject.toml +49 -0
- surreal_sdk/streaming/__init__.py +31 -0
- surreal_sdk/streaming/change_feed.py +278 -0
- surreal_sdk/streaming/live_query.py +265 -0
- surreal_sdk/streaming/live_select.py +369 -0
- surreal_sdk/transaction.py +386 -0
- surreal_sdk/types.py +346 -0
- surrealdb_orm-0.5.1.dist-info/METADATA +465 -0
- surrealdb_orm-0.5.1.dist-info/RECORD +52 -0
- {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.1.dist-info}/WHEEL +1 -1
- surrealdb_orm-0.5.1.dist-info/entry_points.txt +2 -0
- {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.1.dist-info}/licenses/LICENSE +1 -1
- surrealdb_orm-0.1.4.dist-info/METADATA +0 -184
- surrealdb_orm-0.1.4.dist-info/RECORD +0 -12
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
from typing import Any
|
|
2
|
-
from surrealdb import AsyncSurrealDB
|
|
3
|
-
from surrealdb.errors import SurrealDbConnectionError
|
|
4
2
|
import logging
|
|
5
3
|
|
|
4
|
+
from surreal_sdk import HTTPConnection
|
|
5
|
+
from surreal_sdk.exceptions import SurrealDBError
|
|
6
|
+
from surreal_sdk.transaction import HTTPTransaction
|
|
7
|
+
|
|
6
8
|
logger = logging.getLogger(__name__)
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
class SurrealDbConnectionError(Exception):
|
|
12
|
+
"""Connection error for SurrealDB."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
9
17
|
class SurrealDBConnectionManager:
|
|
10
18
|
__url: str | None = None
|
|
11
19
|
__user: str | None = None
|
|
12
20
|
__password: str | None = None
|
|
13
21
|
__namespace: str | None = None
|
|
14
22
|
__database: str | None = None
|
|
15
|
-
__client:
|
|
23
|
+
__client: HTTPConnection | None = None
|
|
16
24
|
|
|
17
25
|
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
18
26
|
await SurrealDBConnectionManager.close_connection()
|
|
19
27
|
|
|
20
|
-
async def __aenter__(self) ->
|
|
28
|
+
async def __aenter__(self) -> HTTPConnection:
|
|
21
29
|
return await SurrealDBConnectionManager.get_client()
|
|
22
30
|
|
|
23
31
|
@classmethod
|
|
@@ -57,31 +65,43 @@ class SurrealDBConnectionManager:
|
|
|
57
65
|
return all([cls.__url, cls.__user, cls.__password, cls.__namespace, cls.__database])
|
|
58
66
|
|
|
59
67
|
@classmethod
|
|
60
|
-
async def get_client(cls) ->
|
|
68
|
+
async def get_client(cls) -> HTTPConnection:
|
|
61
69
|
"""
|
|
62
|
-
Connect to the SurrealDB instance.
|
|
70
|
+
Connect to the SurrealDB instance using the custom SDK.
|
|
63
71
|
|
|
64
|
-
:return: The
|
|
72
|
+
:return: The HTTPConnection instance.
|
|
65
73
|
"""
|
|
66
74
|
|
|
67
|
-
if cls.__client is not None:
|
|
75
|
+
if cls.__client is not None and cls.__client.is_connected:
|
|
68
76
|
return cls.__client
|
|
69
77
|
|
|
70
78
|
if not cls.is_connection_set():
|
|
71
79
|
raise ValueError("Connection not been set.")
|
|
72
80
|
|
|
73
|
-
#
|
|
81
|
+
# Establish connection
|
|
74
82
|
try:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
url = cls.get_connection_string()
|
|
84
|
+
assert url is not None # Already validated by is_connection_set()
|
|
85
|
+
assert cls.__namespace is not None
|
|
86
|
+
assert cls.__database is not None
|
|
87
|
+
assert cls.__user is not None
|
|
88
|
+
assert cls.__password is not None
|
|
89
|
+
|
|
90
|
+
_client = HTTPConnection(url, cls.__namespace, cls.__database)
|
|
91
|
+
await _client.connect()
|
|
92
|
+
await _client.signin(cls.__user, cls.__password)
|
|
79
93
|
|
|
80
94
|
cls.__client = _client
|
|
81
95
|
return cls.__client
|
|
96
|
+
except SurrealDBError as e:
|
|
97
|
+
logger.warning(f"Can't get connection: {e}")
|
|
98
|
+
if cls.__client is not None: # pragma: no cover
|
|
99
|
+
await cls.__client.close()
|
|
100
|
+
cls.__client = None
|
|
101
|
+
raise SurrealDbConnectionError(f"Can't connect to the database: {e}")
|
|
82
102
|
except Exception as e:
|
|
83
103
|
logger.warning(f"Can't get connection: {e}")
|
|
84
|
-
if
|
|
104
|
+
if cls.__client is not None: # pragma: no cover
|
|
85
105
|
await cls.__client.close()
|
|
86
106
|
cls.__client = None
|
|
87
107
|
raise SurrealDbConnectionError("Can't connect to the database.")
|
|
@@ -96,17 +116,19 @@ class SurrealDBConnectionManager:
|
|
|
96
116
|
if cls.__client is None:
|
|
97
117
|
return
|
|
98
118
|
|
|
99
|
-
|
|
119
|
+
try:
|
|
120
|
+
await cls.__client.close()
|
|
121
|
+
except NotImplementedError:
|
|
122
|
+
# close() is not implemented for HTTP connections in surrealdb SDK 1.0.8
|
|
123
|
+
pass
|
|
100
124
|
cls.__client = None
|
|
101
125
|
|
|
102
126
|
@classmethod
|
|
103
|
-
async def reconnect(cls) ->
|
|
127
|
+
async def reconnect(cls) -> HTTPConnection | None:
|
|
104
128
|
"""
|
|
105
129
|
Reconnect to the SurrealDB instance.
|
|
106
130
|
"""
|
|
107
|
-
# Fermer la connexion
|
|
108
131
|
await cls.close_connection()
|
|
109
|
-
# Établir la connexion
|
|
110
132
|
return await cls.get_client()
|
|
111
133
|
|
|
112
134
|
@classmethod
|
|
@@ -299,3 +321,21 @@ class SurrealDBConnectionManager:
|
|
|
299
321
|
"""
|
|
300
322
|
|
|
301
323
|
return cls.__client is not None
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
async def transaction(cls) -> HTTPTransaction:
|
|
327
|
+
"""
|
|
328
|
+
Create a transaction context manager for atomic operations.
|
|
329
|
+
|
|
330
|
+
Usage:
|
|
331
|
+
async with SurrealDBConnectionManager.transaction() as tx:
|
|
332
|
+
user = User(name="Alice")
|
|
333
|
+
await user.save(tx=tx)
|
|
334
|
+
order = Order(user_id=user.id)
|
|
335
|
+
await order.save(tx=tx)
|
|
336
|
+
# Auto-commit on success, auto-rollback on exception
|
|
337
|
+
|
|
338
|
+
:return: HTTPTransaction context manager
|
|
339
|
+
"""
|
|
340
|
+
client = await cls.get_client()
|
|
341
|
+
return client.transaction()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom field types for SurrealDB ORM.
|
|
3
|
+
|
|
4
|
+
This module provides specialized field types that leverage SurrealDB's
|
|
5
|
+
built-in functions for encryption, validation, and other operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .encrypted import Encrypted, EncryptedField, EncryptedFieldInfo
|
|
9
|
+
from .relation import (
|
|
10
|
+
ForeignKey,
|
|
11
|
+
ManyToMany,
|
|
12
|
+
Relation,
|
|
13
|
+
RelationInfo,
|
|
14
|
+
get_relation_info,
|
|
15
|
+
is_foreign_key,
|
|
16
|
+
is_graph_relation,
|
|
17
|
+
is_many_to_many,
|
|
18
|
+
is_relation_field,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Encrypted fields
|
|
23
|
+
"Encrypted",
|
|
24
|
+
"EncryptedField",
|
|
25
|
+
"EncryptedFieldInfo",
|
|
26
|
+
# Relation fields
|
|
27
|
+
"ForeignKey",
|
|
28
|
+
"ManyToMany",
|
|
29
|
+
"Relation",
|
|
30
|
+
"RelationInfo",
|
|
31
|
+
"get_relation_info",
|
|
32
|
+
"is_foreign_key",
|
|
33
|
+
"is_graph_relation",
|
|
34
|
+
"is_many_to_many",
|
|
35
|
+
"is_relation_field",
|
|
36
|
+
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Encrypted field type for password and sensitive data storage.
|
|
3
|
+
|
|
4
|
+
This module provides the Encrypted[T] type that automatically generates
|
|
5
|
+
the appropriate SurrealDB crypto functions in schema definitions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import TYPE_CHECKING, Annotated, Any, get_args, get_origin
|
|
10
|
+
|
|
11
|
+
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
|
|
12
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
13
|
+
from pydantic_core import CoreSchema, core_schema
|
|
14
|
+
|
|
15
|
+
from ..types import EncryptionAlgorithm
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class EncryptedFieldInfo:
|
|
23
|
+
"""
|
|
24
|
+
Metadata for encrypted fields used during schema generation.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
algorithm: The encryption algorithm to use (default: argon2)
|
|
28
|
+
compare_function: SurrealDB function for password comparison
|
|
29
|
+
generate_function: SurrealDB function for password generation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.ARGON2
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def compare_function(self) -> str:
|
|
36
|
+
"""Get the SurrealDB compare function for this algorithm."""
|
|
37
|
+
return f"crypto::{self.algorithm}::compare"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def generate_function(self) -> str:
|
|
41
|
+
"""Get the SurrealDB generate function for this algorithm."""
|
|
42
|
+
return f"crypto::{self.algorithm}::generate"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _EncryptedMarker:
|
|
46
|
+
"""
|
|
47
|
+
Marker class for encrypted fields.
|
|
48
|
+
|
|
49
|
+
This is used internally to mark fields as encrypted in Pydantic's
|
|
50
|
+
type annotation system.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
algorithm: EncryptionAlgorithm
|
|
54
|
+
|
|
55
|
+
def __init__(self, algorithm: EncryptionAlgorithm = EncryptionAlgorithm.ARGON2):
|
|
56
|
+
self.algorithm = algorithm
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def __get_pydantic_core_schema__(
|
|
60
|
+
cls,
|
|
61
|
+
source_type: Any,
|
|
62
|
+
handler: GetCoreSchemaHandler,
|
|
63
|
+
) -> CoreSchema:
|
|
64
|
+
"""Build the Pydantic core schema for validation."""
|
|
65
|
+
# Get algorithm from marker if available
|
|
66
|
+
algorithm = EncryptionAlgorithm.ARGON2
|
|
67
|
+
args = get_args(source_type)
|
|
68
|
+
for arg in args:
|
|
69
|
+
if isinstance(arg, _EncryptedMarker):
|
|
70
|
+
algorithm = arg.algorithm
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
return core_schema.str_schema(
|
|
74
|
+
metadata={
|
|
75
|
+
"encrypted": True,
|
|
76
|
+
"algorithm": str(algorithm),
|
|
77
|
+
"surreal_type": "string",
|
|
78
|
+
"generate_function": f"crypto::{algorithm}::generate",
|
|
79
|
+
"compare_function": f"crypto::{algorithm}::compare",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def __get_pydantic_json_schema__(
|
|
85
|
+
cls,
|
|
86
|
+
core_schema_obj: CoreSchema,
|
|
87
|
+
handler: GetJsonSchemaHandler,
|
|
88
|
+
) -> JsonSchemaValue:
|
|
89
|
+
"""Generate JSON schema for OpenAPI documentation."""
|
|
90
|
+
return {"type": "string", "format": "password"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Type alias for Encrypted[str] using Annotated
|
|
94
|
+
Encrypted = Annotated[str, _EncryptedMarker()]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def EncryptedField(
|
|
98
|
+
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.ARGON2,
|
|
99
|
+
) -> Any:
|
|
100
|
+
"""
|
|
101
|
+
Create an encrypted field type with a specific algorithm.
|
|
102
|
+
|
|
103
|
+
Usage:
|
|
104
|
+
class User(BaseSurrealModel):
|
|
105
|
+
password: EncryptedField(EncryptionAlgorithm.BCRYPT)
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
algorithm: The encryption algorithm to use
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Annotated type with encryption marker
|
|
112
|
+
"""
|
|
113
|
+
return Annotated[str, _EncryptedMarker(algorithm)]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def is_encrypted_field(field_type: Any) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Check if a field type is an Encrypted type.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
field_type: The type annotation to check
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if the field is an Encrypted type
|
|
125
|
+
"""
|
|
126
|
+
# Check for Annotated type with _EncryptedMarker
|
|
127
|
+
origin = get_origin(field_type)
|
|
128
|
+
|
|
129
|
+
if origin is Annotated:
|
|
130
|
+
args = get_args(field_type)
|
|
131
|
+
for arg in args:
|
|
132
|
+
if isinstance(arg, _EncryptedMarker):
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
# Also check for _EncryptedMarker class directly
|
|
136
|
+
if isinstance(field_type, type) and issubclass(field_type, _EncryptedMarker):
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_encryption_info(field_type: Any) -> EncryptedFieldInfo | None:
|
|
143
|
+
"""
|
|
144
|
+
Extract encryption information from a field type.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
field_type: The type annotation to extract from
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
EncryptedFieldInfo if the field is encrypted, None otherwise
|
|
151
|
+
"""
|
|
152
|
+
if not is_encrypted_field(field_type):
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
# Get algorithm from Annotated args
|
|
156
|
+
algorithm = EncryptionAlgorithm.ARGON2
|
|
157
|
+
origin = get_origin(field_type)
|
|
158
|
+
|
|
159
|
+
if origin is Annotated:
|
|
160
|
+
args = get_args(field_type)
|
|
161
|
+
for arg in args:
|
|
162
|
+
if isinstance(arg, _EncryptedMarker):
|
|
163
|
+
algorithm = arg.algorithm
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
return EncryptedFieldInfo(algorithm=algorithm)
|