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.

Files changed (50) hide show
  1. surreal_orm/__init__.py +72 -3
  2. surreal_orm/aggregations.py +164 -0
  3. surreal_orm/auth/__init__.py +15 -0
  4. surreal_orm/auth/access.py +167 -0
  5. surreal_orm/auth/mixins.py +302 -0
  6. surreal_orm/cli/__init__.py +15 -0
  7. surreal_orm/cli/commands.py +369 -0
  8. surreal_orm/connection_manager.py +58 -18
  9. surreal_orm/fields/__init__.py +36 -0
  10. surreal_orm/fields/encrypted.py +166 -0
  11. surreal_orm/fields/relation.py +465 -0
  12. surreal_orm/migrations/__init__.py +51 -0
  13. surreal_orm/migrations/executor.py +380 -0
  14. surreal_orm/migrations/generator.py +272 -0
  15. surreal_orm/migrations/introspector.py +305 -0
  16. surreal_orm/migrations/migration.py +188 -0
  17. surreal_orm/migrations/operations.py +531 -0
  18. surreal_orm/migrations/state.py +406 -0
  19. surreal_orm/model_base.py +530 -44
  20. surreal_orm/query_set.py +609 -33
  21. surreal_orm/relations.py +645 -0
  22. surreal_orm/surreal_function.py +95 -0
  23. surreal_orm/surreal_ql.py +113 -0
  24. surreal_orm/types.py +86 -0
  25. surreal_sdk/README.md +79 -0
  26. surreal_sdk/__init__.py +151 -0
  27. surreal_sdk/connection/__init__.py +17 -0
  28. surreal_sdk/connection/base.py +516 -0
  29. surreal_sdk/connection/http.py +421 -0
  30. surreal_sdk/connection/pool.py +244 -0
  31. surreal_sdk/connection/websocket.py +519 -0
  32. surreal_sdk/exceptions.py +71 -0
  33. surreal_sdk/functions.py +607 -0
  34. surreal_sdk/protocol/__init__.py +13 -0
  35. surreal_sdk/protocol/rpc.py +218 -0
  36. surreal_sdk/py.typed +0 -0
  37. surreal_sdk/pyproject.toml +49 -0
  38. surreal_sdk/streaming/__init__.py +31 -0
  39. surreal_sdk/streaming/change_feed.py +278 -0
  40. surreal_sdk/streaming/live_query.py +265 -0
  41. surreal_sdk/streaming/live_select.py +369 -0
  42. surreal_sdk/transaction.py +386 -0
  43. surreal_sdk/types.py +346 -0
  44. surrealdb_orm-0.5.1.dist-info/METADATA +465 -0
  45. surrealdb_orm-0.5.1.dist-info/RECORD +52 -0
  46. {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.1.dist-info}/WHEEL +1 -1
  47. surrealdb_orm-0.5.1.dist-info/entry_points.txt +2 -0
  48. {surrealdb_orm-0.1.4.dist-info → surrealdb_orm-0.5.1.dist-info}/licenses/LICENSE +1 -1
  49. surrealdb_orm-0.1.4.dist-info/METADATA +0 -184
  50. 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: AsyncSurrealDB | None = None
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) -> AsyncSurrealDB:
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) -> AsyncSurrealDB:
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 SurrealDB instance.
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
- # Établir la connexion
81
+ # Establish connection
74
82
  try:
75
- _client = AsyncSurrealDB(cls.get_connection_string())
76
- await _client.connect() # type: ignore
77
- await _client.use(cls.__namespace, cls.__database) # type: ignore
78
- await _client.sign_in(cls.__user, cls.__password) # type: ignore
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 isinstance(cls.__client, AsyncSurrealDB): # pragma: no cover
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
- await cls.__client.close()
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) -> AsyncSurrealDB | None:
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)