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
@@ -0,0 +1,465 @@
1
+ """
2
+ Relation field types for SurrealDB ORM.
3
+
4
+ This module provides relation field types that leverage SurrealDB's
5
+ graph capabilities for defining relationships between models.
6
+
7
+ Example:
8
+ from surreal_orm import BaseSurrealModel
9
+ from surreal_orm.fields import ForeignKey, ManyToMany, Relation
10
+
11
+ class User(BaseSurrealModel):
12
+ id: str | None = None
13
+ name: str
14
+
15
+ # Graph relations (SurrealDB edges)
16
+ followers: Relation("follows", "User", reverse=True)
17
+ following: Relation("follows", "User")
18
+
19
+ # Traditional relations
20
+ profile: ForeignKey("Profile", on_delete="CASCADE")
21
+ groups: ManyToMany("Group", through="membership")
22
+ """
23
+
24
+ from dataclasses import dataclass
25
+ from typing import TYPE_CHECKING, Annotated, Any, Literal, get_args, get_origin
26
+
27
+ from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
28
+ from pydantic.json_schema import JsonSchemaValue
29
+ from pydantic_core import CoreSchema, core_schema
30
+
31
+ if TYPE_CHECKING:
32
+ pass
33
+
34
+
35
+ @dataclass
36
+ class RelationInfo:
37
+ """
38
+ Metadata for relation fields used during schema generation and querying.
39
+
40
+ Attributes:
41
+ to_model: The target model name (string to allow forward references)
42
+ relation_type: Type of relation (foreign_key, many_to_many, relation)
43
+ edge_table: Name of the edge table for graph relations
44
+ reverse: Whether to traverse in reverse direction (<- vs ->)
45
+ on_delete: Delete behavior for foreign keys (CASCADE, SET_NULL, PROTECT)
46
+ related_name: Name for the reverse relation on the target model
47
+ through: Intermediate table for many-to-many relations
48
+ """
49
+
50
+ to_model: str
51
+ relation_type: Literal["foreign_key", "many_to_many", "relation"]
52
+ edge_table: str | None = None
53
+ reverse: bool = False
54
+ on_delete: Literal["CASCADE", "SET_NULL", "PROTECT"] | None = None
55
+ related_name: str | None = None
56
+ through: str | None = None
57
+
58
+ @property
59
+ def traversal_direction(self) -> str:
60
+ """Get the SurrealDB traversal direction operator."""
61
+ return "<-" if self.reverse else "->"
62
+
63
+ def get_traversal_query(self, from_table: str, from_id: str) -> str:
64
+ """
65
+ Generate the SurrealDB traversal query.
66
+
67
+ Args:
68
+ from_table: Source table name
69
+ from_id: Source record ID
70
+
71
+ Returns:
72
+ SurrealQL traversal query string
73
+ """
74
+ if self.relation_type == "relation":
75
+ if self.reverse:
76
+ return f"SELECT * FROM {from_table}:{from_id}<-{self.edge_table}<-{self.to_model}"
77
+ return f"SELECT * FROM {from_table}:{from_id}->{self.edge_table}->{self.to_model}"
78
+ elif self.relation_type == "foreign_key":
79
+ return f"SELECT * FROM {self.to_model} WHERE id = {from_table}:{from_id}.{self.edge_table}"
80
+ else: # many_to_many
81
+ through = self.through or f"{from_table}_{self.to_model}"
82
+ return f"SELECT * FROM {from_table}:{from_id}->{through}->{self.to_model}"
83
+
84
+
85
+ class _ForeignKeyMarker:
86
+ """
87
+ Marker class for ForeignKey fields.
88
+
89
+ Foreign keys represent a single reference to another record,
90
+ stored as a record ID in the source table.
91
+ """
92
+
93
+ to: str
94
+ on_delete: Literal["CASCADE", "SET_NULL", "PROTECT"]
95
+ related_name: str | None
96
+
97
+ def __init__(
98
+ self,
99
+ to: str,
100
+ on_delete: Literal["CASCADE", "SET_NULL", "PROTECT"] = "CASCADE",
101
+ related_name: str | None = None,
102
+ ):
103
+ self.to = to
104
+ self.on_delete = on_delete
105
+ self.related_name = related_name
106
+
107
+ @classmethod
108
+ def __get_pydantic_core_schema__(
109
+ cls,
110
+ source_type: Any,
111
+ handler: GetCoreSchemaHandler,
112
+ ) -> CoreSchema:
113
+ """Build the Pydantic core schema for validation."""
114
+ # ForeignKey stores a record ID (string) or None
115
+ to_model = ""
116
+ on_delete: Literal["CASCADE", "SET_NULL", "PROTECT"] = "CASCADE"
117
+ related_name = None
118
+
119
+ args = get_args(source_type)
120
+ for arg in args:
121
+ if isinstance(arg, _ForeignKeyMarker):
122
+ to_model = arg.to
123
+ on_delete = arg.on_delete
124
+ related_name = arg.related_name
125
+ break
126
+
127
+ return core_schema.nullable_schema(
128
+ core_schema.str_schema(
129
+ metadata={
130
+ "relation_type": "foreign_key",
131
+ "to_model": to_model,
132
+ "on_delete": on_delete,
133
+ "related_name": related_name,
134
+ "surreal_type": "record",
135
+ }
136
+ )
137
+ )
138
+
139
+ @classmethod
140
+ def __get_pydantic_json_schema__(
141
+ cls,
142
+ core_schema_obj: CoreSchema,
143
+ handler: GetJsonSchemaHandler,
144
+ ) -> JsonSchemaValue:
145
+ """Generate JSON schema for OpenAPI documentation."""
146
+ return {"type": "string", "format": "record-id", "nullable": True}
147
+
148
+
149
+ class _ManyToManyMarker:
150
+ """
151
+ Marker class for ManyToMany fields.
152
+
153
+ Many-to-many relations are implemented using SurrealDB graph edges,
154
+ with an optional intermediate table for storing relation metadata.
155
+ """
156
+
157
+ to: str
158
+ through: str | None
159
+ related_name: str | None
160
+
161
+ def __init__(
162
+ self,
163
+ to: str,
164
+ through: str | None = None,
165
+ related_name: str | None = None,
166
+ ):
167
+ self.to = to
168
+ self.through = through
169
+ self.related_name = related_name
170
+
171
+ @classmethod
172
+ def __get_pydantic_core_schema__(
173
+ cls,
174
+ source_type: Any,
175
+ handler: GetCoreSchemaHandler,
176
+ ) -> CoreSchema:
177
+ """Build the Pydantic core schema for validation."""
178
+ to_model = ""
179
+ through = None
180
+ related_name = None
181
+
182
+ args = get_args(source_type)
183
+ for arg in args:
184
+ if isinstance(arg, _ManyToManyMarker):
185
+ to_model = arg.to
186
+ through = arg.through
187
+ related_name = arg.related_name
188
+ break
189
+
190
+ # ManyToMany is represented as a list of record IDs (virtual field)
191
+ return core_schema.list_schema(
192
+ core_schema.str_schema(),
193
+ metadata={
194
+ "relation_type": "many_to_many",
195
+ "to_model": to_model,
196
+ "through": through,
197
+ "related_name": related_name,
198
+ "surreal_type": "virtual",
199
+ },
200
+ )
201
+
202
+ @classmethod
203
+ def __get_pydantic_json_schema__(
204
+ cls,
205
+ core_schema_obj: CoreSchema,
206
+ handler: GetJsonSchemaHandler,
207
+ ) -> JsonSchemaValue:
208
+ """Generate JSON schema for OpenAPI documentation."""
209
+ return {"type": "array", "items": {"type": "string", "format": "record-id"}}
210
+
211
+
212
+ class _RelationMarker:
213
+ """
214
+ Marker class for graph Relation fields.
215
+
216
+ Relations use SurrealDB's native graph capabilities with RELATE
217
+ statements and edge traversal (-> and <-).
218
+ """
219
+
220
+ edge: str
221
+ to: str
222
+ reverse: bool
223
+
224
+ def __init__(
225
+ self,
226
+ edge: str,
227
+ to: str,
228
+ reverse: bool = False,
229
+ ):
230
+ self.edge = edge
231
+ self.to = to
232
+ self.reverse = reverse
233
+
234
+ @classmethod
235
+ def __get_pydantic_core_schema__(
236
+ cls,
237
+ source_type: Any,
238
+ handler: GetCoreSchemaHandler,
239
+ ) -> CoreSchema:
240
+ """Build the Pydantic core schema for validation."""
241
+ edge = ""
242
+ to_model = ""
243
+ reverse = False
244
+
245
+ args = get_args(source_type)
246
+ for arg in args:
247
+ if isinstance(arg, _RelationMarker):
248
+ edge = arg.edge
249
+ to_model = arg.to
250
+ reverse = arg.reverse
251
+ break
252
+
253
+ # Relation is represented as a list of related objects (virtual field)
254
+ return core_schema.list_schema(
255
+ core_schema.any_schema(),
256
+ metadata={
257
+ "relation_type": "relation",
258
+ "edge_table": edge,
259
+ "to_model": to_model,
260
+ "reverse": reverse,
261
+ "surreal_type": "virtual",
262
+ },
263
+ )
264
+
265
+ @classmethod
266
+ def __get_pydantic_json_schema__(
267
+ cls,
268
+ core_schema_obj: CoreSchema,
269
+ handler: GetJsonSchemaHandler,
270
+ ) -> JsonSchemaValue:
271
+ """Generate JSON schema for OpenAPI documentation."""
272
+ return {"type": "array", "items": {"type": "object"}}
273
+
274
+
275
+ def ForeignKey(
276
+ to: str,
277
+ on_delete: Literal["CASCADE", "SET_NULL", "PROTECT"] = "CASCADE",
278
+ related_name: str | None = None,
279
+ ) -> Any:
280
+ """
281
+ Create a ForeignKey field type.
282
+
283
+ A ForeignKey represents a single reference to another model,
284
+ stored as a record ID in the database.
285
+
286
+ Args:
287
+ to: Target model name (string for forward references)
288
+ on_delete: Behavior when referenced record is deleted
289
+ - CASCADE: Delete this record too
290
+ - SET_NULL: Set the field to null
291
+ - PROTECT: Prevent deletion of referenced record
292
+ related_name: Name for reverse relation on target model
293
+
294
+ Returns:
295
+ Annotated type for use in model definition
296
+
297
+ Example:
298
+ class Post(BaseSurrealModel):
299
+ author: ForeignKey("User", related_name="posts")
300
+ """
301
+ return Annotated[str | None, _ForeignKeyMarker(to, on_delete, related_name)]
302
+
303
+
304
+ def ManyToMany(
305
+ to: str,
306
+ through: str | None = None,
307
+ related_name: str | None = None,
308
+ ) -> Any:
309
+ """
310
+ Create a ManyToMany field type.
311
+
312
+ A ManyToMany relation uses SurrealDB graph edges to connect
313
+ multiple records. An optional intermediate table can store
314
+ additional metadata about the relationship.
315
+
316
+ Args:
317
+ to: Target model name (string for forward references)
318
+ through: Intermediate edge table name (auto-generated if not specified)
319
+ related_name: Name for reverse relation on target model
320
+
321
+ Returns:
322
+ Annotated type for use in model definition
323
+
324
+ Example:
325
+ class User(BaseSurrealModel):
326
+ groups: ManyToMany("Group", through="membership")
327
+
328
+ class Group(BaseSurrealModel):
329
+ members: ManyToMany("User", through="membership", related_name="groups")
330
+ """
331
+ return Annotated[list, _ManyToManyMarker(to, through, related_name)]
332
+
333
+
334
+ def Relation(
335
+ edge: str,
336
+ to: str,
337
+ reverse: bool = False,
338
+ ) -> Any:
339
+ """
340
+ Create a graph Relation field type.
341
+
342
+ A Relation uses SurrealDB's native graph capabilities for
343
+ traversing edges between records. This is the most flexible
344
+ relation type, supporting arbitrary graph structures.
345
+
346
+ Args:
347
+ edge: Name of the edge table (e.g., "follows", "likes")
348
+ to: Target model name (string for forward references)
349
+ reverse: Whether to traverse in reverse direction
350
+ - False (default): Forward traversal (->)
351
+ - True: Reverse traversal (<-)
352
+
353
+ Returns:
354
+ Annotated type for use in model definition
355
+
356
+ Example:
357
+ class User(BaseSurrealModel):
358
+ # People this user follows (outgoing edges)
359
+ following: Relation("follows", "User")
360
+
361
+ # People who follow this user (incoming edges)
362
+ followers: Relation("follows", "User", reverse=True)
363
+
364
+ SurrealQL equivalent:
365
+ - following: SELECT * FROM user:id->follows->User
366
+ - followers: SELECT * FROM user:id<-follows<-User
367
+ """
368
+ return Annotated[list, _RelationMarker(edge, to, reverse)]
369
+
370
+
371
+ def is_relation_field(field_type: Any) -> bool:
372
+ """
373
+ Check if a field type is a relation type.
374
+
375
+ Args:
376
+ field_type: The type annotation to check
377
+
378
+ Returns:
379
+ True if the field is a ForeignKey, ManyToMany, or Relation
380
+ """
381
+ origin = get_origin(field_type)
382
+
383
+ if origin is Annotated:
384
+ args = get_args(field_type)
385
+ for arg in args:
386
+ if isinstance(arg, (_ForeignKeyMarker, _ManyToManyMarker, _RelationMarker)):
387
+ return True
388
+
389
+ return False
390
+
391
+
392
+ def is_foreign_key(field_type: Any) -> bool:
393
+ """Check if a field type is a ForeignKey."""
394
+ origin = get_origin(field_type)
395
+ if origin is Annotated:
396
+ args = get_args(field_type)
397
+ for arg in args:
398
+ if isinstance(arg, _ForeignKeyMarker):
399
+ return True
400
+ return False
401
+
402
+
403
+ def is_many_to_many(field_type: Any) -> bool:
404
+ """Check if a field type is a ManyToMany relation."""
405
+ origin = get_origin(field_type)
406
+ if origin is Annotated:
407
+ args = get_args(field_type)
408
+ for arg in args:
409
+ if isinstance(arg, _ManyToManyMarker):
410
+ return True
411
+ return False
412
+
413
+
414
+ def is_graph_relation(field_type: Any) -> bool:
415
+ """Check if a field type is a graph Relation."""
416
+ origin = get_origin(field_type)
417
+ if origin is Annotated:
418
+ args = get_args(field_type)
419
+ for arg in args:
420
+ if isinstance(arg, _RelationMarker):
421
+ return True
422
+ return False
423
+
424
+
425
+ def get_relation_info(field_type: Any) -> RelationInfo | None:
426
+ """
427
+ Extract relation information from a field type.
428
+
429
+ Args:
430
+ field_type: The type annotation to extract from
431
+
432
+ Returns:
433
+ RelationInfo if the field is a relation, None otherwise
434
+ """
435
+ if not is_relation_field(field_type):
436
+ return None
437
+
438
+ origin = get_origin(field_type)
439
+
440
+ if origin is Annotated:
441
+ args = get_args(field_type)
442
+ for arg in args:
443
+ if isinstance(arg, _ForeignKeyMarker):
444
+ return RelationInfo(
445
+ to_model=arg.to,
446
+ relation_type="foreign_key",
447
+ on_delete=arg.on_delete,
448
+ related_name=arg.related_name,
449
+ )
450
+ elif isinstance(arg, _ManyToManyMarker):
451
+ return RelationInfo(
452
+ to_model=arg.to,
453
+ relation_type="many_to_many",
454
+ through=arg.through,
455
+ related_name=arg.related_name,
456
+ )
457
+ elif isinstance(arg, _RelationMarker):
458
+ return RelationInfo(
459
+ to_model=arg.to,
460
+ relation_type="relation",
461
+ edge_table=arg.edge,
462
+ reverse=arg.reverse,
463
+ )
464
+
465
+ return None
@@ -0,0 +1,51 @@
1
+ """
2
+ SurrealDB ORM Migration System.
3
+
4
+ This module provides Django-style migrations for SurrealDB, including:
5
+ - Schema versioning with migration files
6
+ - Auto-detection of model changes
7
+ - Forward and backward migrations
8
+ - Data migrations for record transformations
9
+
10
+ Usage:
11
+ # Generate migrations from model changes
12
+ await manager.makemigrations(name="initial")
13
+
14
+ # Apply pending migrations
15
+ await manager.migrate()
16
+
17
+ # Rollback to a specific migration
18
+ await manager.rollback("0001_initial")
19
+ """
20
+
21
+ from .migration import Migration
22
+ from .operations import (
23
+ AddField,
24
+ AlterField,
25
+ CreateIndex,
26
+ CreateTable,
27
+ DataMigration,
28
+ DefineAccess,
29
+ DropField,
30
+ DropIndex,
31
+ DropTable,
32
+ Operation,
33
+ RawSQL,
34
+ RemoveAccess,
35
+ )
36
+
37
+ __all__ = [
38
+ "Migration",
39
+ "Operation",
40
+ "CreateTable",
41
+ "DropTable",
42
+ "AddField",
43
+ "DropField",
44
+ "AlterField",
45
+ "CreateIndex",
46
+ "DropIndex",
47
+ "DefineAccess",
48
+ "RemoveAccess",
49
+ "DataMigration",
50
+ "RawSQL",
51
+ ]