surrealdb-orm 0.1.3__py3-none-any.whl → 0.5.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.
Files changed (51) hide show
  1. surreal_orm/__init__.py +78 -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 +594 -135
  20. surreal_orm/py.typed +0 -0
  21. surreal_orm/query_set.py +609 -34
  22. surreal_orm/relations.py +645 -0
  23. surreal_orm/surreal_function.py +95 -0
  24. surreal_orm/surreal_ql.py +113 -0
  25. surreal_orm/types.py +86 -0
  26. surreal_sdk/README.md +79 -0
  27. surreal_sdk/__init__.py +151 -0
  28. surreal_sdk/connection/__init__.py +17 -0
  29. surreal_sdk/connection/base.py +516 -0
  30. surreal_sdk/connection/http.py +421 -0
  31. surreal_sdk/connection/pool.py +244 -0
  32. surreal_sdk/connection/websocket.py +519 -0
  33. surreal_sdk/exceptions.py +71 -0
  34. surreal_sdk/functions.py +607 -0
  35. surreal_sdk/protocol/__init__.py +13 -0
  36. surreal_sdk/protocol/rpc.py +218 -0
  37. surreal_sdk/py.typed +0 -0
  38. surreal_sdk/pyproject.toml +49 -0
  39. surreal_sdk/streaming/__init__.py +31 -0
  40. surreal_sdk/streaming/change_feed.py +278 -0
  41. surreal_sdk/streaming/live_query.py +265 -0
  42. surreal_sdk/streaming/live_select.py +369 -0
  43. surreal_sdk/transaction.py +386 -0
  44. surreal_sdk/types.py +346 -0
  45. surrealdb_orm-0.5.0.dist-info/METADATA +465 -0
  46. surrealdb_orm-0.5.0.dist-info/RECORD +52 -0
  47. {surrealdb_orm-0.1.3.dist-info → surrealdb_orm-0.5.0.dist-info}/WHEEL +1 -1
  48. surrealdb_orm-0.5.0.dist-info/entry_points.txt +2 -0
  49. {surrealdb_orm-0.1.3.dist-info → surrealdb_orm-0.5.0.dist-info}/licenses/LICENSE +1 -1
  50. surrealdb_orm-0.1.3.dist-info/METADATA +0 -184
  51. surrealdb_orm-0.1.3.dist-info/RECORD +0 -11
surreal_sdk/types.py ADDED
@@ -0,0 +1,346 @@
1
+ """
2
+ Type definitions for SurrealDB SDK responses.
3
+
4
+ Provides strongly-typed wrappers around SurrealDB responses instead of raw Any types.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from enum import Enum
9
+ from typing import Any
10
+
11
+
12
+ class ResponseStatus(str, Enum):
13
+ """Status of a SurrealDB response."""
14
+
15
+ OK = "OK"
16
+ ERR = "ERR"
17
+
18
+
19
+ @dataclass
20
+ class QueryResult:
21
+ """
22
+ Result of a single query statement.
23
+
24
+ Attributes:
25
+ status: OK or ERR
26
+ result: The query result data (records, scalar, etc.)
27
+ time: Execution time as reported by SurrealDB
28
+ """
29
+
30
+ status: ResponseStatus
31
+ result: list[dict[str, Any]] | dict[str, Any] | str | int | float | bool | None
32
+ time: str = ""
33
+
34
+ @classmethod
35
+ def from_dict(cls, data: dict[str, Any]) -> "QueryResult":
36
+ """Parse a query result from raw response dict."""
37
+ status = ResponseStatus(data.get("status", "OK"))
38
+ result = data.get("result")
39
+ time = data.get("time", "")
40
+ return cls(status=status, result=result, time=time)
41
+
42
+ @property
43
+ def is_ok(self) -> bool:
44
+ """Check if query succeeded."""
45
+ return self.status == ResponseStatus.OK
46
+
47
+ @property
48
+ def is_error(self) -> bool:
49
+ """Check if query failed."""
50
+ return self.status == ResponseStatus.ERR
51
+
52
+ @property
53
+ def records(self) -> list[dict[str, Any]]:
54
+ """Get result as list of records. Returns empty list if not applicable."""
55
+ if isinstance(self.result, list):
56
+ return self.result
57
+ return []
58
+
59
+ @property
60
+ def first(self) -> dict[str, Any] | None:
61
+ """Get first record or None."""
62
+ records = self.records
63
+ return records[0] if records else None
64
+
65
+ @property
66
+ def scalar(self) -> str | int | float | bool | None:
67
+ """Get result as scalar value."""
68
+ if isinstance(self.result, (str, int, float, bool)):
69
+ return self.result
70
+ return None
71
+
72
+
73
+ @dataclass
74
+ class QueryResponse:
75
+ """
76
+ Response from a SurrealDB query operation.
77
+
78
+ Contains one or more QueryResult objects (one per statement in the query).
79
+ """
80
+
81
+ results: list[QueryResult] = field(default_factory=list)
82
+ raw: dict[str, Any] | list[Any] = field(default_factory=dict)
83
+
84
+ @classmethod
85
+ def from_rpc_result(cls, data: Any) -> "QueryResponse":
86
+ """Parse query response from RPC result."""
87
+ results: list[QueryResult] = []
88
+
89
+ if isinstance(data, list):
90
+ for item in data:
91
+ if isinstance(item, dict) and "status" in item:
92
+ results.append(QueryResult.from_dict(item))
93
+ elif isinstance(item, dict):
94
+ # Direct result without status wrapper
95
+ results.append(QueryResult(status=ResponseStatus.OK, result=item))
96
+ else:
97
+ results.append(QueryResult(status=ResponseStatus.OK, result=item))
98
+ elif isinstance(data, dict):
99
+ if "status" in data:
100
+ results.append(QueryResult.from_dict(data))
101
+ else:
102
+ results.append(QueryResult(status=ResponseStatus.OK, result=data))
103
+
104
+ return cls(results=results, raw=data if data else {})
105
+
106
+ @property
107
+ def is_ok(self) -> bool:
108
+ """Check if all results succeeded."""
109
+ return all(r.is_ok for r in self.results)
110
+
111
+ @property
112
+ def first_result(self) -> QueryResult | None:
113
+ """Get first query result."""
114
+ return self.results[0] if self.results else None
115
+
116
+ @property
117
+ def all_records(self) -> list[dict[str, Any]]:
118
+ """Get all records from all results."""
119
+ records: list[dict[str, Any]] = []
120
+ for result in self.results:
121
+ records.extend(result.records)
122
+ return records
123
+
124
+ @property
125
+ def is_empty(self) -> bool:
126
+ """Check if response contains no records."""
127
+ return len(self.all_records) == 0
128
+
129
+ @property
130
+ def first(self) -> dict[str, Any] | None:
131
+ """Get first record from all results or None."""
132
+ records = self.all_records
133
+ return records[0] if records else None
134
+
135
+
136
+ @dataclass
137
+ class RecordResponse:
138
+ """
139
+ Response for single record operations (create, select one, update, etc.).
140
+ """
141
+
142
+ record: dict[str, Any] | None = None
143
+ raw: Any = None
144
+
145
+ @classmethod
146
+ def from_rpc_result(cls, data: Any) -> "RecordResponse":
147
+ """Parse record response from RPC result."""
148
+ record: dict[str, Any] | None = None
149
+
150
+ if isinstance(data, dict):
151
+ record = data
152
+ elif isinstance(data, list) and len(data) > 0:
153
+ if isinstance(data[0], dict):
154
+ record = data[0]
155
+
156
+ return cls(record=record, raw=data)
157
+
158
+ @property
159
+ def exists(self) -> bool:
160
+ """Check if record exists."""
161
+ return self.record is not None
162
+
163
+ def get(self, key: str, default: Any = None) -> Any:
164
+ """Get field from record."""
165
+ if self.record:
166
+ return self.record.get(key, default)
167
+ return default
168
+
169
+ @property
170
+ def id(self) -> str | None:
171
+ """Get record ID."""
172
+ value = self.get("id")
173
+ return str(value) if value is not None else None
174
+
175
+
176
+ @dataclass
177
+ class RecordsResponse:
178
+ """
179
+ Response for multiple records operations (select all, etc.).
180
+ """
181
+
182
+ records: list[dict[str, Any]] = field(default_factory=list)
183
+ raw: Any = None
184
+
185
+ @classmethod
186
+ def from_rpc_result(cls, data: Any) -> "RecordsResponse":
187
+ """Parse records response from RPC result."""
188
+ records: list[dict[str, Any]] = []
189
+
190
+ if isinstance(data, list):
191
+ for item in data:
192
+ if isinstance(item, dict):
193
+ records.append(item)
194
+ elif isinstance(data, dict):
195
+ records.append(data)
196
+
197
+ return cls(records=records, raw=data)
198
+
199
+ @property
200
+ def count(self) -> int:
201
+ """Get number of records."""
202
+ return len(self.records)
203
+
204
+ @property
205
+ def is_empty(self) -> bool:
206
+ """Check if no records returned."""
207
+ return len(self.records) == 0
208
+
209
+ @property
210
+ def first(self) -> dict[str, Any] | None:
211
+ """Get first record or None."""
212
+ return self.records[0] if self.records else None
213
+
214
+ def __iter__(self) -> Any:
215
+ """Iterate over records."""
216
+ return iter(self.records)
217
+
218
+ def __len__(self) -> int:
219
+ """Get number of records."""
220
+ return len(self.records)
221
+
222
+
223
+ @dataclass
224
+ class AuthResponse:
225
+ """
226
+ Response from authentication operations.
227
+ """
228
+
229
+ token: str | None = None
230
+ success: bool = False
231
+ raw: Any = None
232
+
233
+ @classmethod
234
+ def from_rpc_result(cls, data: Any) -> "AuthResponse":
235
+ """Parse auth response from RPC result."""
236
+ token: str | None = None
237
+ success = False
238
+
239
+ if isinstance(data, str):
240
+ token = data
241
+ success = True
242
+ elif data is None:
243
+ # signin/signup with no token return = success
244
+ success = True
245
+
246
+ return cls(token=token, success=success, raw=data)
247
+
248
+
249
+ @dataclass
250
+ class InfoResponse:
251
+ """
252
+ Response from INFO operations.
253
+ """
254
+
255
+ data: dict[str, Any] = field(default_factory=dict)
256
+ raw: Any = None
257
+
258
+ @classmethod
259
+ def from_rpc_result(cls, data: Any) -> "InfoResponse":
260
+ """Parse info response from RPC result."""
261
+ info_data: dict[str, Any] = {}
262
+
263
+ if isinstance(data, dict):
264
+ info_data = data
265
+ elif isinstance(data, list) and len(data) > 0:
266
+ if isinstance(data[0], dict):
267
+ info_data = data[0]
268
+
269
+ return cls(data=info_data, raw=data)
270
+
271
+ @property
272
+ def tables(self) -> dict[str, Any]:
273
+ """Get tables info."""
274
+ result = self.data.get("tables", self.data.get("tb", {}))
275
+ return result if isinstance(result, dict) else {}
276
+
277
+ @property
278
+ def namespaces(self) -> dict[str, Any]:
279
+ """Get namespaces info."""
280
+ result = self.data.get("namespaces", self.data.get("ns", {}))
281
+ return result if isinstance(result, dict) else {}
282
+
283
+ @property
284
+ def databases(self) -> dict[str, Any]:
285
+ """Get databases info."""
286
+ result = self.data.get("databases", self.data.get("db", {}))
287
+ return result if isinstance(result, dict) else {}
288
+
289
+
290
+ @dataclass
291
+ class LiveQueryId:
292
+ """
293
+ Wrapper for Live Query UUID.
294
+ """
295
+
296
+ uuid: str
297
+
298
+ def __str__(self) -> str:
299
+ return self.uuid
300
+
301
+ @classmethod
302
+ def from_rpc_result(cls, data: Any) -> "LiveQueryId":
303
+ """Parse live query ID from RPC result."""
304
+ if isinstance(data, str):
305
+ return cls(uuid=data)
306
+ elif isinstance(data, list) and len(data) > 0:
307
+ first = data[0]
308
+ if isinstance(first, str):
309
+ return cls(uuid=first)
310
+ elif isinstance(first, dict) and "result" in first:
311
+ return cls(uuid=str(first["result"]))
312
+ raise ValueError(f"Cannot parse live query ID from: {data}")
313
+
314
+
315
+ @dataclass
316
+ class DeleteResponse:
317
+ """
318
+ Response from delete operations.
319
+ """
320
+
321
+ deleted: list[dict[str, Any]] = field(default_factory=list)
322
+ raw: Any = None
323
+
324
+ @classmethod
325
+ def from_rpc_result(cls, data: Any) -> "DeleteResponse":
326
+ """Parse delete response from RPC result."""
327
+ deleted: list[dict[str, Any]] = []
328
+
329
+ if isinstance(data, list):
330
+ for item in data:
331
+ if isinstance(item, dict):
332
+ deleted.append(item)
333
+ elif isinstance(data, dict):
334
+ deleted.append(data)
335
+
336
+ return cls(deleted=deleted, raw=data)
337
+
338
+ @property
339
+ def count(self) -> int:
340
+ """Number of deleted records."""
341
+ return len(self.deleted)
342
+
343
+ @property
344
+ def success(self) -> bool:
345
+ """Check if any records were deleted."""
346
+ return len(self.deleted) > 0