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,406 @@
1
+ """
2
+ Schema state tracking and diffing for migrations.
3
+
4
+ This module provides classes to represent the current schema state
5
+ and compute the differences between two states to generate migrations.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from .operations import Operation
13
+
14
+
15
+ @dataclass
16
+ class FieldState:
17
+ """
18
+ Represents the state of a single field in a table.
19
+
20
+ Attributes:
21
+ name: Field name
22
+ field_type: SurrealDB type (string, int, etc.)
23
+ nullable: Whether the field can be null
24
+ default: Default value if any
25
+ assertion: Validation assertion
26
+ encrypted: Whether the field is encrypted
27
+ flexible: Whether the field accepts additional types
28
+ readonly: Whether the field is read-only
29
+ value: VALUE clause for computed fields
30
+ """
31
+
32
+ name: str
33
+ field_type: str
34
+ nullable: bool = True
35
+ default: Any = None
36
+ assertion: str | None = None
37
+ encrypted: bool = False
38
+ flexible: bool = False
39
+ readonly: bool = False
40
+ value: str | None = None
41
+
42
+ def __eq__(self, other: object) -> bool:
43
+ if not isinstance(other, FieldState):
44
+ return False
45
+ return (
46
+ self.name == other.name
47
+ and self.field_type == other.field_type
48
+ and self.nullable == other.nullable
49
+ and self.default == other.default
50
+ and self.assertion == other.assertion
51
+ and self.encrypted == other.encrypted
52
+ and self.flexible == other.flexible
53
+ and self.readonly == other.readonly
54
+ and self.value == other.value
55
+ )
56
+
57
+ def has_changed(self, other: "FieldState") -> bool:
58
+ """Check if field definition has changed from other."""
59
+ return self != other
60
+
61
+
62
+ @dataclass
63
+ class IndexState:
64
+ """
65
+ Represents the state of an index on a table.
66
+
67
+ Attributes:
68
+ name: Index name
69
+ fields: List of field names in the index
70
+ unique: Whether the index enforces uniqueness
71
+ search_analyzer: Full-text search analyzer if any
72
+ """
73
+
74
+ name: str
75
+ fields: list[str]
76
+ unique: bool = False
77
+ search_analyzer: str | None = None
78
+
79
+ def __eq__(self, other: object) -> bool:
80
+ if not isinstance(other, IndexState):
81
+ return False
82
+ return (
83
+ self.name == other.name
84
+ and self.fields == other.fields
85
+ and self.unique == other.unique
86
+ and self.search_analyzer == other.search_analyzer
87
+ )
88
+
89
+
90
+ @dataclass
91
+ class AccessState:
92
+ """
93
+ Represents the state of an access definition for authentication.
94
+
95
+ Attributes:
96
+ name: Access name
97
+ table: Associated table
98
+ signup_fields: Fields set during signup
99
+ signin_where: WHERE clause for signin
100
+ duration_token: Token duration
101
+ duration_session: Session duration
102
+ """
103
+
104
+ name: str
105
+ table: str
106
+ signup_fields: dict[str, str]
107
+ signin_where: str
108
+ duration_token: str = "15m"
109
+ duration_session: str = "12h"
110
+
111
+ def __eq__(self, other: object) -> bool:
112
+ if not isinstance(other, AccessState):
113
+ return False
114
+ return (
115
+ self.name == other.name
116
+ and self.table == other.table
117
+ and self.signup_fields == other.signup_fields
118
+ and self.signin_where == other.signin_where
119
+ and self.duration_token == other.duration_token
120
+ and self.duration_session == other.duration_session
121
+ )
122
+
123
+
124
+ @dataclass
125
+ class TableState:
126
+ """
127
+ Represents the complete state of a table.
128
+
129
+ Attributes:
130
+ name: Table name
131
+ schema_mode: SCHEMAFULL or SCHEMALESS
132
+ table_type: Table type classification (normal, user, stream, hash)
133
+ fields: Dict of field name to FieldState
134
+ indexes: Dict of index name to IndexState
135
+ changefeed: Changefeed duration if enabled
136
+ permissions: Dict of action to WHERE condition
137
+ access: Access definition if this is a USER table
138
+ """
139
+
140
+ name: str
141
+ schema_mode: str = "SCHEMAFULL"
142
+ table_type: str = "normal"
143
+ fields: dict[str, FieldState] = field(default_factory=dict)
144
+ indexes: dict[str, IndexState] = field(default_factory=dict)
145
+ changefeed: str | None = None
146
+ permissions: dict[str, str] = field(default_factory=dict)
147
+ access: AccessState | None = None
148
+
149
+ def __eq__(self, other: object) -> bool:
150
+ if not isinstance(other, TableState):
151
+ return False
152
+ return (
153
+ self.name == other.name
154
+ and self.schema_mode == other.schema_mode
155
+ and self.table_type == other.table_type
156
+ and self.fields == other.fields
157
+ and self.indexes == other.indexes
158
+ and self.changefeed == other.changefeed
159
+ and self.permissions == other.permissions
160
+ and self.access == other.access
161
+ )
162
+
163
+
164
+ @dataclass
165
+ class SchemaState:
166
+ """
167
+ Represents the complete database schema state.
168
+
169
+ This class tracks all tables and their definitions, and can compute
170
+ the operations needed to transform one state into another.
171
+
172
+ Attributes:
173
+ tables: Dict of table name to TableState
174
+ applied_migrations: List of already applied migration names
175
+ """
176
+
177
+ tables: dict[str, TableState] = field(default_factory=dict)
178
+ applied_migrations: list[str] = field(default_factory=list)
179
+
180
+ def diff(self, target: "SchemaState") -> list["Operation"]:
181
+ """
182
+ Compute operations needed to transform this state into target state.
183
+
184
+ Args:
185
+ target: The desired schema state
186
+
187
+ Returns:
188
+ List of operations to apply (CreateTable, AddField, etc.)
189
+ """
190
+ from .operations import (
191
+ AddField,
192
+ AlterField,
193
+ CreateIndex,
194
+ CreateTable,
195
+ DefineAccess,
196
+ DropField,
197
+ DropIndex,
198
+ DropTable,
199
+ RemoveAccess,
200
+ )
201
+
202
+ operations: list[Operation] = []
203
+
204
+ # Tables to create (in target but not in self)
205
+ for table_name, target_table in target.tables.items():
206
+ if table_name not in self.tables:
207
+ # Create the table
208
+ operations.append(
209
+ CreateTable(
210
+ name=table_name,
211
+ schema_mode=target_table.schema_mode,
212
+ table_type=target_table.table_type,
213
+ changefeed=target_table.changefeed,
214
+ permissions=target_table.permissions or None,
215
+ )
216
+ )
217
+ # Add all fields
218
+ for field_name, field_state in target_table.fields.items():
219
+ operations.append(
220
+ AddField(
221
+ table=table_name,
222
+ name=field_name,
223
+ field_type=field_state.field_type,
224
+ default=field_state.default,
225
+ assertion=field_state.assertion,
226
+ encrypted=field_state.encrypted,
227
+ flexible=field_state.flexible,
228
+ readonly=field_state.readonly,
229
+ value=field_state.value,
230
+ )
231
+ )
232
+ # Add all indexes
233
+ for index_name, index_state in target_table.indexes.items():
234
+ operations.append(
235
+ CreateIndex(
236
+ table=table_name,
237
+ name=index_name,
238
+ fields=index_state.fields,
239
+ unique=index_state.unique,
240
+ search_analyzer=index_state.search_analyzer,
241
+ )
242
+ )
243
+ # Add access definition if present
244
+ if target_table.access:
245
+ operations.append(
246
+ DefineAccess(
247
+ name=target_table.access.name,
248
+ table=table_name,
249
+ signup_fields=target_table.access.signup_fields,
250
+ signin_where=target_table.access.signin_where,
251
+ duration_token=target_table.access.duration_token,
252
+ duration_session=target_table.access.duration_session,
253
+ )
254
+ )
255
+
256
+ # Tables to drop (in self but not in target)
257
+ for table_name in self.tables:
258
+ if table_name not in target.tables:
259
+ operations.append(DropTable(name=table_name))
260
+
261
+ # Tables to modify (in both)
262
+ for table_name, target_table in target.tables.items():
263
+ if table_name in self.tables:
264
+ current_table = self.tables[table_name]
265
+
266
+ # Check if table definition changed
267
+ if (
268
+ current_table.schema_mode != target_table.schema_mode
269
+ or current_table.changefeed != target_table.changefeed
270
+ or current_table.permissions != target_table.permissions
271
+ ):
272
+ # Recreate table definition (DEFINE TABLE is idempotent)
273
+ operations.append(
274
+ CreateTable(
275
+ name=table_name,
276
+ schema_mode=target_table.schema_mode,
277
+ table_type=target_table.table_type,
278
+ changefeed=target_table.changefeed,
279
+ permissions=target_table.permissions or None,
280
+ )
281
+ )
282
+
283
+ # Fields to add
284
+ for field_name, field_state in target_table.fields.items():
285
+ if field_name not in current_table.fields:
286
+ operations.append(
287
+ AddField(
288
+ table=table_name,
289
+ name=field_name,
290
+ field_type=field_state.field_type,
291
+ default=field_state.default,
292
+ assertion=field_state.assertion,
293
+ encrypted=field_state.encrypted,
294
+ flexible=field_state.flexible,
295
+ readonly=field_state.readonly,
296
+ value=field_state.value,
297
+ )
298
+ )
299
+ elif current_table.fields[field_name] != field_state:
300
+ # Field changed
301
+ current_field = current_table.fields[field_name]
302
+ operations.append(
303
+ AlterField(
304
+ table=table_name,
305
+ name=field_name,
306
+ field_type=field_state.field_type,
307
+ default=field_state.default,
308
+ assertion=field_state.assertion,
309
+ encrypted=field_state.encrypted,
310
+ flexible=field_state.flexible,
311
+ readonly=field_state.readonly,
312
+ value=field_state.value,
313
+ previous_type=current_field.field_type,
314
+ previous_default=current_field.default,
315
+ previous_assertion=current_field.assertion,
316
+ )
317
+ )
318
+
319
+ # Fields to drop
320
+ for field_name in current_table.fields:
321
+ if field_name not in target_table.fields:
322
+ operations.append(DropField(table=table_name, name=field_name))
323
+
324
+ # Indexes to add
325
+ for index_name, index_state in target_table.indexes.items():
326
+ if index_name not in current_table.indexes:
327
+ operations.append(
328
+ CreateIndex(
329
+ table=table_name,
330
+ name=index_name,
331
+ fields=index_state.fields,
332
+ unique=index_state.unique,
333
+ search_analyzer=index_state.search_analyzer,
334
+ )
335
+ )
336
+ elif current_table.indexes[index_name] != index_state:
337
+ # Index changed - drop and recreate
338
+ operations.append(DropIndex(table=table_name, name=index_name))
339
+ operations.append(
340
+ CreateIndex(
341
+ table=table_name,
342
+ name=index_name,
343
+ fields=index_state.fields,
344
+ unique=index_state.unique,
345
+ search_analyzer=index_state.search_analyzer,
346
+ )
347
+ )
348
+
349
+ # Indexes to drop
350
+ for index_name in current_table.indexes:
351
+ if index_name not in target_table.indexes:
352
+ operations.append(DropIndex(table=table_name, name=index_name))
353
+
354
+ # Access definition changes
355
+ if target_table.access and not current_table.access:
356
+ # Add access
357
+ operations.append(
358
+ DefineAccess(
359
+ name=target_table.access.name,
360
+ table=table_name,
361
+ signup_fields=target_table.access.signup_fields,
362
+ signin_where=target_table.access.signin_where,
363
+ duration_token=target_table.access.duration_token,
364
+ duration_session=target_table.access.duration_session,
365
+ )
366
+ )
367
+ elif current_table.access and not target_table.access:
368
+ # Remove access
369
+ operations.append(RemoveAccess(name=current_table.access.name))
370
+ elif target_table.access and current_table.access and target_table.access != current_table.access:
371
+ # Update access (remove old, add new)
372
+ operations.append(RemoveAccess(name=current_table.access.name))
373
+ operations.append(
374
+ DefineAccess(
375
+ name=target_table.access.name,
376
+ table=table_name,
377
+ signup_fields=target_table.access.signup_fields,
378
+ signin_where=target_table.access.signin_where,
379
+ duration_token=target_table.access.duration_token,
380
+ duration_session=target_table.access.duration_session,
381
+ )
382
+ )
383
+
384
+ return operations
385
+
386
+ def clone(self) -> "SchemaState":
387
+ """Create a deep copy of this schema state."""
388
+ import copy
389
+
390
+ return copy.deepcopy(self)
391
+
392
+ def add_table(self, table: TableState) -> None:
393
+ """Add a table to the schema."""
394
+ self.tables[table.name] = table
395
+
396
+ def remove_table(self, name: str) -> None:
397
+ """Remove a table from the schema."""
398
+ if name in self.tables:
399
+ del self.tables[name]
400
+
401
+ def get_table(self, name: str) -> TableState | None:
402
+ """Get a table by name."""
403
+ return self.tables.get(name)
404
+
405
+ def __repr__(self) -> str:
406
+ return f"SchemaState(tables={list(self.tables.keys())}, migrations={len(self.applied_migrations)})"