tapestry-orm 0.0.2__tar.gz → 0.0.4__tar.gz
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.
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/PKG-INFO +4 -11
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/README.md +1 -8
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/pyproject.toml +3 -3
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/__init__.py +2 -4
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/base.py +24 -28
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/edge.py +19 -39
- tapestry_orm-0.0.4/src/tapestry/engine.py +388 -0
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/field.py +11 -8
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/field.pyi +4 -4
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/node.py +8 -26
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/query.py +7 -7
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/table.py +170 -87
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/tokenizer.py +20 -15
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/utils.py +6 -5
- tapestry_orm-0.0.2/src/tapestry/engine.py +0 -398
- {tapestry_orm-0.0.2 → tapestry_orm-0.0.4}/src/tapestry/py.typed +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tapestry-orm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A prototype ORM for SurrealDB
|
|
5
5
|
Author-email: timothee@obrecht.xyz
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Requires-Dist: more-itertools>=10.7.0
|
|
8
8
|
Requires-Dist: pydantic>=2.11.7
|
|
9
|
-
Requires-Dist: surrealdb
|
|
9
|
+
Requires-Dist: surrealdb==2.0.0a1
|
|
10
10
|
Requires-Dist: sphinx>=7.2.0 ; extra == 'docs'
|
|
11
11
|
Requires-Dist: sphinx-rtd-theme>=2.0.0 ; extra == 'docs'
|
|
12
12
|
Requires-Dist: sphinx-autodoc-typehints>=2.0.0 ; extra == 'docs'
|
|
@@ -15,7 +15,7 @@ Requires-Dist: myst-parser>=2.0.0 ; extra == 'docs'
|
|
|
15
15
|
Requires-Dist: sphinx-autobuild>=2021.3.14 ; extra == 'docs'
|
|
16
16
|
Requires-Dist: sphinx-autoapi>=3.0.0 ; extra == 'docs'
|
|
17
17
|
Requires-Python: >=3.12
|
|
18
|
-
Project-URL: Documentation, https://readthedocs.
|
|
18
|
+
Project-URL: Documentation, https://tapestry.readthedocs.io/en/latest/
|
|
19
19
|
Project-URL: Repository, https://gitlab.com/timete_OS/tapestry-orm
|
|
20
20
|
Provides-Extra: docs
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -338,9 +338,6 @@ Works with **mypy** and **pyright** for catching errors at build time!
|
|
|
338
338
|
# Run all tests
|
|
339
339
|
uv run pytest
|
|
340
340
|
|
|
341
|
-
# Run with coverage
|
|
342
|
-
uv run pytest --cov=tapestry
|
|
343
|
-
|
|
344
341
|
# Run specific test
|
|
345
342
|
uv run pytest tests/test_complete.py::TestWorkflow::test_select_queries
|
|
346
343
|
```
|
|
@@ -363,11 +360,7 @@ This gives you an identical testing experience to the actual CI pipeline, ensuri
|
|
|
363
360
|
### Type Checking
|
|
364
361
|
|
|
365
362
|
```bash
|
|
366
|
-
|
|
367
|
-
uv run mypy src/tapestry
|
|
368
|
-
|
|
369
|
-
# With pyright
|
|
370
|
-
uv run pyright src/tapestry
|
|
363
|
+
uv run basedpyright src/tapestry
|
|
371
364
|
```
|
|
372
365
|
|
|
373
366
|
### Building Documentation
|
|
@@ -316,9 +316,6 @@ Works with **mypy** and **pyright** for catching errors at build time!
|
|
|
316
316
|
# Run all tests
|
|
317
317
|
uv run pytest
|
|
318
318
|
|
|
319
|
-
# Run with coverage
|
|
320
|
-
uv run pytest --cov=tapestry
|
|
321
|
-
|
|
322
319
|
# Run specific test
|
|
323
320
|
uv run pytest tests/test_complete.py::TestWorkflow::test_select_queries
|
|
324
321
|
```
|
|
@@ -341,11 +338,7 @@ This gives you an identical testing experience to the actual CI pipeline, ensuri
|
|
|
341
338
|
### Type Checking
|
|
342
339
|
|
|
343
340
|
```bash
|
|
344
|
-
|
|
345
|
-
uv run mypy src/tapestry
|
|
346
|
-
|
|
347
|
-
# With pyright
|
|
348
|
-
uv run pyright src/tapestry
|
|
341
|
+
uv run basedpyright src/tapestry
|
|
349
342
|
```
|
|
350
343
|
|
|
351
344
|
### Building Documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tapestry-orm"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.4"
|
|
4
4
|
description = "A prototype ORM for SurrealDB"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -13,11 +13,11 @@ requires-python = ">=3.12"
|
|
|
13
13
|
dependencies = [
|
|
14
14
|
"more-itertools>=10.7.0",
|
|
15
15
|
"pydantic>=2.11.7",
|
|
16
|
-
"surrealdb
|
|
16
|
+
"surrealdb==2.0.0-alpha.1",
|
|
17
17
|
]
|
|
18
18
|
[project.urls]
|
|
19
19
|
Repository = "https://gitlab.com/timete_OS/tapestry-orm"
|
|
20
|
-
Documentation = "https://readthedocs.
|
|
20
|
+
Documentation = "https://tapestry.readthedocs.io/en/latest/"
|
|
21
21
|
|
|
22
22
|
[project.optional-dependencies]
|
|
23
23
|
docs = [
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
__all__ = [
|
|
2
|
-
"Base", "Text", "create_engine", "create_engine_context", "Engine", "Reference", "Node", "Edge", "Q"
|
|
3
|
-
]
|
|
1
|
+
__all__ = ["Base", "Text", "create_engine", "create_engine_context", "Engine", "Reference", "Indexed", "Node", "Edge", "Q"]
|
|
4
2
|
|
|
5
3
|
from .query import Q
|
|
6
4
|
from .base import Base
|
|
7
5
|
from .node import Node
|
|
8
6
|
from .edge import Edge
|
|
9
7
|
from .tokenizer import Text
|
|
10
|
-
from .table import Reference
|
|
8
|
+
from .table import Reference, Indexed
|
|
11
9
|
from .engine import create_engine, create_engine_context, Engine
|
|
@@ -9,7 +9,7 @@ from copy import deepcopy
|
|
|
9
9
|
from itertools import chain
|
|
10
10
|
from queue import LifoQueue
|
|
11
11
|
from surrealdb import RecordID
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Any, ClassVar, Unpack, get_origin, get_args
|
|
13
13
|
from pydantic_core.core_schema import ValidationInfo
|
|
14
14
|
from pydantic.fields import FieldInfo, ComputedFieldInfo
|
|
15
15
|
from pydantic import BaseModel, field_validator, model_serializer, Field, ConfigDict
|
|
@@ -57,12 +57,13 @@ class Base(BaseModel):
|
|
|
57
57
|
- Handles enum serialization automatically
|
|
58
58
|
- Provides full-text search capabilities with Text fields
|
|
59
59
|
"""
|
|
60
|
-
|
|
60
|
+
|
|
61
|
+
id: RecordID | None = Field(exclude=False, default=None)
|
|
61
62
|
|
|
62
63
|
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
63
64
|
arbitrary_types_allowed=True,
|
|
64
|
-
use_enum_values=False,
|
|
65
|
-
validate_assignment=True
|
|
65
|
+
use_enum_values=False, # would be great to have a 'use_enum_names'
|
|
66
|
+
validate_assignment=True,
|
|
66
67
|
)
|
|
67
68
|
_registry: ClassVar[tuple[Table, ...]] = ()
|
|
68
69
|
_tokenizers: ClassVar[set[str]] = set()
|
|
@@ -70,9 +71,8 @@ class Base(BaseModel):
|
|
|
70
71
|
_discarded: ClassVar[bool] = False
|
|
71
72
|
child_classes: ClassVar[dict[str, type[Base]]] = {}
|
|
72
73
|
|
|
73
|
-
|
|
74
74
|
@classmethod
|
|
75
|
-
@field_validator(
|
|
75
|
+
@field_validator("id")
|
|
76
76
|
def validate_record_id(cls, v: Any) -> RecordID | None:
|
|
77
77
|
"""
|
|
78
78
|
Validate and convert record ID values.
|
|
@@ -94,7 +94,6 @@ class Base(BaseModel):
|
|
|
94
94
|
# Convert string to RecordID if needed
|
|
95
95
|
return RecordID(cls.__name__.lower(), v)
|
|
96
96
|
|
|
97
|
-
|
|
98
97
|
@model_serializer(mode="wrap")
|
|
99
98
|
def _serialize(self, serializer, info):
|
|
100
99
|
"""
|
|
@@ -125,8 +124,7 @@ class Base(BaseModel):
|
|
|
125
124
|
# maybe check here in context if we are inserting in db
|
|
126
125
|
return self.id
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
@field_validator('*', mode='before')
|
|
127
|
+
@field_validator("*", mode="before")
|
|
130
128
|
@classmethod
|
|
131
129
|
def validate_enums(cls, v: Any, info: ValidationInfo) -> Any:
|
|
132
130
|
"""
|
|
@@ -165,7 +163,6 @@ class Base(BaseModel):
|
|
|
165
163
|
# return stub
|
|
166
164
|
return v
|
|
167
165
|
|
|
168
|
-
|
|
169
166
|
def __init_subclass__(cls, **kwargs: Unpack[ConfigDict]):
|
|
170
167
|
discarded = kwargs.pop("discarded", False)
|
|
171
168
|
# Create unique queue for each subclass
|
|
@@ -174,7 +171,6 @@ class Base(BaseModel):
|
|
|
174
171
|
cls._discarded = bool(discarded)
|
|
175
172
|
return super().__init_subclass__(**kwargs)
|
|
176
173
|
|
|
177
|
-
|
|
178
174
|
@classmethod
|
|
179
175
|
def __pydantic_init_subclass__(cls, **kwargs):
|
|
180
176
|
relation = kwargs.pop("relation", None)
|
|
@@ -190,7 +186,9 @@ class Base(BaseModel):
|
|
|
190
186
|
for field_name, field_info in model_fields.items():
|
|
191
187
|
field_type = field_info.annotation if isinstance(field_info, FieldInfo) else field_info.return_type
|
|
192
188
|
if isinstance(field_info, ComputedFieldInfo):
|
|
193
|
-
field_descriptor = ComputedFieldDescriptor[field_type](
|
|
189
|
+
field_descriptor = ComputedFieldDescriptor[field_type](
|
|
190
|
+
field_name, cls.__name__.lower(), field_type or Any, getattr(cls, field_name, None)
|
|
191
|
+
)
|
|
194
192
|
else:
|
|
195
193
|
field_descriptor = CustomField[field_type](field_name, cls.__name__.lower(), field_type or Any)
|
|
196
194
|
fields.append(field_descriptor)
|
|
@@ -199,15 +197,19 @@ class Base(BaseModel):
|
|
|
199
197
|
nested_class = None
|
|
200
198
|
try:
|
|
201
199
|
# Check if it's a Base subclass directly
|
|
202
|
-
if field_type and hasattr(field_type,
|
|
200
|
+
if field_type and hasattr(field_type, "__mro__") and Base in field_type.__mro__:
|
|
203
201
|
nested_class = field_type
|
|
204
202
|
# Check if it's Optional[Base subclass] (Union with None)
|
|
205
|
-
elif get_origin(field_type) is types.UnionType or (
|
|
203
|
+
elif get_origin(field_type) is types.UnionType or (
|
|
204
|
+
get_origin(field_type) is not None
|
|
205
|
+
and hasattr(get_origin(field_type), "__name__")
|
|
206
|
+
and get_origin(field_type).__name__ == "UnionType"
|
|
207
|
+
):
|
|
206
208
|
# Get the args of the Union
|
|
207
209
|
args = get_args(field_type)
|
|
208
210
|
# Filter out None and check if any remaining type is a Base subclass
|
|
209
211
|
for arg in args:
|
|
210
|
-
if arg is not type(None) and hasattr(arg,
|
|
212
|
+
if arg is not type(None) and hasattr(arg, "__mro__") and Base in arg.__mro__:
|
|
211
213
|
nested_class = arg
|
|
212
214
|
break
|
|
213
215
|
|
|
@@ -223,8 +225,9 @@ class Base(BaseModel):
|
|
|
223
225
|
|
|
224
226
|
# replace Base instance with Union[annotation, RecordID]
|
|
225
227
|
if field_name in cls.model_fields:
|
|
226
|
-
cls.model_fields[field_name].annotation = replace_type(
|
|
227
|
-
|
|
228
|
+
cls.model_fields[field_name].annotation = replace_type(
|
|
229
|
+
cls.model_fields[field_name].annotation, Base, Base | RecordID
|
|
230
|
+
)
|
|
228
231
|
|
|
229
232
|
table = Table(name=cls.__name__.lower(), base_class=Base, model_class=cls, fields=tuple(fields), relation=relation)
|
|
230
233
|
for tokenizer in table.tokenizers:
|
|
@@ -235,7 +238,6 @@ class Base(BaseModel):
|
|
|
235
238
|
Base.add_table(table, cls)
|
|
236
239
|
cls.model_rebuild(force=True)
|
|
237
240
|
|
|
238
|
-
|
|
239
241
|
@classmethod
|
|
240
242
|
def registered_tables(cls) -> set[str]:
|
|
241
243
|
"""
|
|
@@ -251,7 +253,6 @@ class Base(BaseModel):
|
|
|
251
253
|
"""
|
|
252
254
|
return {f.name for f in cls._registry}
|
|
253
255
|
|
|
254
|
-
|
|
255
256
|
@classmethod
|
|
256
257
|
def add_table(cls, table: Table, child_class: type[Base]):
|
|
257
258
|
"""
|
|
@@ -265,10 +266,9 @@ class Base(BaseModel):
|
|
|
265
266
|
This is called automatically when subclasses are defined.
|
|
266
267
|
Users typically don't need to call this directly.
|
|
267
268
|
"""
|
|
268
|
-
cls._registry = tuple(t for t in chain(cls._registry, (table,
|
|
269
|
+
cls._registry = tuple(t for t in chain(cls._registry, (table,)))
|
|
269
270
|
cls.child_classes[table.name] = child_class
|
|
270
271
|
|
|
271
|
-
|
|
272
272
|
@classmethod
|
|
273
273
|
def registered_models(cls) -> list[Table]:
|
|
274
274
|
"""
|
|
@@ -284,7 +284,6 @@ class Base(BaseModel):
|
|
|
284
284
|
"""
|
|
285
285
|
return list(cls._registry)
|
|
286
286
|
|
|
287
|
-
|
|
288
287
|
@classmethod
|
|
289
288
|
def generate_schema(cls) -> str:
|
|
290
289
|
"""
|
|
@@ -314,7 +313,6 @@ class Base(BaseModel):
|
|
|
314
313
|
blocks = [t.generate_table_sql() for t in cls._registry]
|
|
315
314
|
return "\n\n".join(chain(cls._tokenizers, blocks))
|
|
316
315
|
|
|
317
|
-
|
|
318
316
|
@classmethod
|
|
319
317
|
def deserialize_record(cls, data: dict) -> Any:
|
|
320
318
|
"""
|
|
@@ -331,7 +329,7 @@ class Base(BaseModel):
|
|
|
331
329
|
if no matching model is found
|
|
332
330
|
|
|
333
331
|
Notes:
|
|
334
|
-
- Handles edge records by converting
|
|
332
|
+
- Handles edge records by converting ``in`` and ``out`` to ``in_`` and ``out_``
|
|
335
333
|
- Automatically determines the model class from the record ID
|
|
336
334
|
- Validates data using Pydantic validation
|
|
337
335
|
"""
|
|
@@ -343,7 +341,7 @@ class Base(BaseModel):
|
|
|
343
341
|
data["out_"] = data.pop("out", None)
|
|
344
342
|
|
|
345
343
|
# Get the model class from the record ID
|
|
346
|
-
record_id = data.get(
|
|
344
|
+
record_id = data.get("id")
|
|
347
345
|
if not record_id or not isinstance(record_id, RecordID):
|
|
348
346
|
return data
|
|
349
347
|
|
|
@@ -355,7 +353,6 @@ class Base(BaseModel):
|
|
|
355
353
|
# here maybe specify if the users wants related records as ids or full objects
|
|
356
354
|
return model_class.model_validate(data, context="could this parameter be of any use ?")
|
|
357
355
|
|
|
358
|
-
|
|
359
356
|
@classmethod
|
|
360
357
|
def deserialize_response(cls, response: Any) -> Any:
|
|
361
358
|
"""
|
|
@@ -381,7 +378,6 @@ class Base(BaseModel):
|
|
|
381
378
|
return cls.deserialize_record(response)
|
|
382
379
|
return response
|
|
383
380
|
|
|
384
|
-
|
|
385
381
|
def db_dump(self) -> dict[str, Any]:
|
|
386
382
|
"""
|
|
387
383
|
Serialize the model instance for database insertion/update.
|
|
@@ -400,6 +396,6 @@ class Base(BaseModel):
|
|
|
400
396
|
>>> await db.create("person", data)
|
|
401
397
|
"""
|
|
402
398
|
serialized = self.model_dump(context={"root": True})
|
|
403
|
-
serialized.pop(
|
|
399
|
+
serialized.pop("id", None)
|
|
404
400
|
# this convert_types fonction is only here
|
|
405
401
|
return convert_types(serialized)
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
-
from
|
|
3
|
-
from typing import Self, Iterable, get_type_hints, Any, ClassVar, TYPE_CHECKING
|
|
4
|
-
from pydantic.fields import FieldInfo, ComputedFieldInfo
|
|
2
|
+
from typing import Self, Iterable, get_type_hints, Any
|
|
5
3
|
from surrealdb import AsyncWsSurrealConnection, AsyncHttpSurrealConnection, RecordID
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
from .base import Base
|
|
9
7
|
from .node import Node
|
|
10
8
|
from .utils import replace_type
|
|
11
|
-
from .table import
|
|
12
|
-
from .field import Traversal, Direction
|
|
9
|
+
from .table import Link, flatten_type
|
|
10
|
+
from .field import Traversal, Direction
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class Edge(Base, ABC, discarded=True):
|
|
@@ -18,14 +16,14 @@ class Edge(Base, ABC, discarded=True):
|
|
|
18
16
|
|
|
19
17
|
Edge represents relationship tables in SurrealDB that connect two nodes.
|
|
20
18
|
All relationship tables should inherit from this class and define
|
|
21
|
-
|
|
19
|
+
``in_`` and ``out_`` fields to specify the connected node types.
|
|
22
20
|
|
|
23
21
|
Class Attributes:
|
|
24
22
|
_directed (bool): Whether the relationship is directional (default: True)
|
|
25
23
|
|
|
26
24
|
Required Fields:
|
|
27
|
-
in_
|
|
28
|
-
out_
|
|
25
|
+
``in_``: The source node of the relationship
|
|
26
|
+
``out_``: The target node of the relationship
|
|
29
27
|
|
|
30
28
|
Example:
|
|
31
29
|
>>> from tapestry import Edge, Node
|
|
@@ -54,12 +52,13 @@ class Edge(Base, ABC, discarded=True):
|
|
|
54
52
|
>>> await belongs.relate(db)
|
|
55
53
|
|
|
56
54
|
Notes:
|
|
57
|
-
- Requires both
|
|
55
|
+
- Requires both ``in_`` and ``out_`` fields to be defined
|
|
58
56
|
- Automatically creates SurrealDB RELATION tables
|
|
59
57
|
- Supports directional and bidirectional relationships
|
|
60
|
-
- Can have additional fields beyond in_ and out_
|
|
58
|
+
- Can have additional fields beyond ``in_`` and ``out_``
|
|
61
59
|
- Use relate() instead of create() for edge records
|
|
62
60
|
"""
|
|
61
|
+
|
|
63
62
|
# in_: Node
|
|
64
63
|
# out_: Node
|
|
65
64
|
#
|
|
@@ -76,7 +75,7 @@ class Edge(Base, ABC, discarded=True):
|
|
|
76
75
|
**kwargs: Additional configuration passed to parent
|
|
77
76
|
|
|
78
77
|
Raises:
|
|
79
|
-
TypeError: If the subclass doesn't define both
|
|
78
|
+
TypeError: If the subclass doesn't define both ``in_`` and ``out_`` fields
|
|
80
79
|
"""
|
|
81
80
|
super().__init_subclass__(**kwargs)
|
|
82
81
|
|
|
@@ -84,20 +83,17 @@ class Edge(Base, ABC, discarded=True):
|
|
|
84
83
|
hints = get_type_hints(cls)
|
|
85
84
|
|
|
86
85
|
# Check if required fields are annotated
|
|
87
|
-
required_fields = {
|
|
86
|
+
required_fields = {"in_", "out_"}
|
|
88
87
|
missing_fields = required_fields - set(hints.keys())
|
|
89
88
|
if missing_fields:
|
|
90
|
-
raise TypeError(
|
|
91
|
-
f"Class {cls.__name__} must have field annotations for: {missing_fields}"
|
|
92
|
-
)
|
|
89
|
+
raise TypeError(f"Class {cls.__name__} must have field annotations for: {missing_fields}")
|
|
93
90
|
|
|
94
91
|
cls._directed = directed
|
|
95
92
|
|
|
96
|
-
|
|
97
93
|
@classmethod
|
|
98
94
|
def __pydantic_init_subclass__(cls, **kwargs):
|
|
99
95
|
relation = None
|
|
100
|
-
in_, out_ = cls.model_fields.pop(
|
|
96
|
+
in_, out_ = cls.model_fields.pop("in_", None), cls.model_fields.pop("out_", None)
|
|
101
97
|
if in_ or out_:
|
|
102
98
|
if in_ and out_:
|
|
103
99
|
assert in_.annotation
|
|
@@ -119,14 +115,12 @@ class Edge(Base, ABC, discarded=True):
|
|
|
119
115
|
cls.model_fields["out_"] = out_
|
|
120
116
|
cls.model_rebuild(force=True)
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
124
118
|
def db_dump(self) -> dict[str, Any]:
|
|
125
119
|
"""
|
|
126
120
|
Serialize the edge instance for database insertion.
|
|
127
121
|
|
|
128
122
|
Converts Python field names to SurrealDB format by renaming
|
|
129
|
-
|
|
123
|
+
``in_`` to ``in`` and ``out_`` to ``out``.
|
|
130
124
|
|
|
131
125
|
Returns:
|
|
132
126
|
dict[str, Any]: Dictionary ready for SurrealDB relation operations
|
|
@@ -136,11 +130,7 @@ class Edge(Base, ABC, discarded=True):
|
|
|
136
130
|
dump["out"] = dump.pop("out_")
|
|
137
131
|
return dump
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
async def relate(
|
|
141
|
-
self,
|
|
142
|
-
db: AsyncWsSurrealConnection | AsyncHttpSurrealConnection
|
|
143
|
-
) -> Self:
|
|
133
|
+
async def relate(self, db: AsyncWsSurrealConnection | AsyncHttpSurrealConnection) -> Self:
|
|
144
134
|
"""
|
|
145
135
|
Create a relationship record in the database.
|
|
146
136
|
|
|
@@ -168,29 +158,22 @@ class Edge(Base, ABC, discarded=True):
|
|
|
168
158
|
Exception: If called on a non-relation table
|
|
169
159
|
|
|
170
160
|
Notes:
|
|
171
|
-
- Both in_ and out_ nodes must exist in the database
|
|
161
|
+
- Both ``in_`` and ``out_`` nodes must exist in the database
|
|
172
162
|
- Creates a directed or bidirectional edge based on class configuration
|
|
173
163
|
- The edge record gets a unique ID from SurrealDB
|
|
174
164
|
"""
|
|
175
165
|
if not self.is_relation:
|
|
176
166
|
raise Exception("You should use .create() to create a record")
|
|
177
167
|
thing = self.__class__.__name__.lower()
|
|
178
|
-
relation = await db.insert_relation(
|
|
179
|
-
thing,
|
|
180
|
-
self.db_dump()
|
|
181
|
-
)
|
|
168
|
+
relation = await db.insert_relation(thing, self.db_dump())
|
|
182
169
|
if isinstance(relation, list):
|
|
183
170
|
self.id = relation[0]["id"]
|
|
184
171
|
else:
|
|
185
172
|
self.id = relation["id"]
|
|
186
173
|
return self
|
|
187
174
|
|
|
188
|
-
|
|
189
175
|
@classmethod
|
|
190
|
-
async def insert(
|
|
191
|
-
cls,
|
|
192
|
-
db: AsyncWsSurrealConnection | AsyncHttpSurrealConnection, others: Iterable[Self]
|
|
193
|
-
) -> list[Self]:
|
|
176
|
+
async def insert(cls, db: AsyncWsSurrealConnection | AsyncHttpSurrealConnection, others: Iterable[Self]) -> list[Self]:
|
|
194
177
|
"""
|
|
195
178
|
Batch insert multiple edge records into the database.
|
|
196
179
|
|
|
@@ -218,10 +201,7 @@ class Edge(Base, ABC, discarded=True):
|
|
|
218
201
|
- Original instances are modified with IDs
|
|
219
202
|
"""
|
|
220
203
|
others = list(others)
|
|
221
|
-
inserted = await db.insert_relation(
|
|
222
|
-
cls.__name__.lower(),
|
|
223
|
-
[other.db_dump() for other in others]
|
|
224
|
-
)
|
|
204
|
+
inserted = await db.insert_relation(cls.__name__.lower(), [other.db_dump() for other in others])
|
|
225
205
|
for insert, original in zip(inserted, others):
|
|
226
206
|
original.id = insert["id"]
|
|
227
207
|
return others
|