Skip to content

Commit 2f7b5bc

Browse files
authored
Merge pull request #1442 from Jaseci-Labs/feature/savable-object
[FEATURE]: Savable Object
2 parents 1a79005 + 34277b9 commit 2f7b5bc

File tree

12 files changed

+812
-33
lines changed

12 files changed

+812
-33
lines changed

jac-cloud/jac_cloud/core/architype.py

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@
3131
Access as _Access,
3232
AccessLevel,
3333
Anchor,
34-
Architype,
3534
DSFunc,
3635
EdgeAnchor as _EdgeAnchor,
3736
EdgeArchitype as _EdgeArchitype,
3837
NodeAnchor as _NodeAnchor,
3938
NodeArchitype as _NodeArchitype,
39+
ObjectAnchor as _ObjectAnchor,
40+
ObjectArchitype as _ObjectArchitype,
4041
Permission as _Permission,
4142
TANCH,
4243
WalkerAnchor as _WalkerAnchor,
@@ -53,10 +54,11 @@
5354
from ..jaseci.utils import logger
5455

5556
MANUAL_SAVE = getenv("MANUAL_SAVE")
56-
GENERIC_ID_REGEX = compile(r"^(n|e|w):([^:]*):([a-f\d]{24})$", IGNORECASE)
57+
GENERIC_ID_REGEX = compile(r"^(n|e|w|o):([^:]*):([a-f\d]{24})$", IGNORECASE)
5758
NODE_ID_REGEX = compile(r"^n:([^:]*):([a-f\d]{24})$", IGNORECASE)
5859
EDGE_ID_REGEX = compile(r"^e:([^:]*):([a-f\d]{24})$", IGNORECASE)
5960
WALKER_ID_REGEX = compile(r"^w:([^:]*):([a-f\d]{24})$", IGNORECASE)
61+
OBJECT_ID_REGEX = compile(r"^o:([^:]*):([a-f\d]{24})$", IGNORECASE)
6062
T = TypeVar("T")
6163
TBA = TypeVar("TBA", bound="BaseArchitype")
6264

@@ -167,12 +169,14 @@ class BulkWrite:
167169
NodeAnchor: [],
168170
EdgeAnchor: [],
169171
WalkerAnchor: [],
172+
ObjectAnchor: [],
170173
}
171174
)
172175

173176
del_ops_nodes: list[ObjectId] = field(default_factory=list)
174177
del_ops_edges: list[ObjectId] = field(default_factory=list)
175178
del_ops_walker: list[ObjectId] = field(default_factory=list)
179+
del_ops_object: list[ObjectId] = field(default_factory=list)
176180

177181
def del_node(self, id: ObjectId) -> None:
178182
"""Add node to delete many operations."""
@@ -201,6 +205,15 @@ def del_walker(self, id: ObjectId) -> None:
201205

202206
self.del_ops_walker.append(id)
203207

208+
def del_object(self, id: ObjectId) -> None:
209+
"""Add walker to delete many operations."""
210+
if not self.del_ops_object:
211+
self.operations[ObjectAnchor].append(
212+
DeleteMany({"_id": {"$in": self.del_ops_object}})
213+
)
214+
215+
self.del_ops_object.append(id)
216+
204217
@property
205218
def has_operations(self) -> bool:
206219
"""Check if has operations."""
@@ -244,6 +257,8 @@ def execute(self, session: ClientSession) -> None:
244257
EdgeAnchor.Collection.bulk_write(edge_operation, False, session)
245258
if walker_operation := self.operations[WalkerAnchor]:
246259
WalkerAnchor.Collection.bulk_write(walker_operation, False, session)
260+
if object_operation := self.operations[ObjectAnchor]:
261+
ObjectAnchor.Collection.bulk_write(object_operation, False, session)
247262
self.commit(session)
248263
break
249264
except (ConnectionFailure, OperationFailure) as ex:
@@ -346,13 +361,15 @@ def ref(ref_id: str) -> "BaseAnchor | Anchor":
346361
cls = EdgeAnchor
347362
case "w":
348363
cls = WalkerAnchor
364+
case "o":
365+
cls = ObjectAnchor
349366
case _:
350-
raise ValueError(f"{ref_id}] is not a valid reference!")
367+
raise ValueError(f"[{ref_id}] is not a valid reference!")
351368
anchor = object.__new__(cls)
352369
anchor.name = str(match.group(2))
353370
anchor.id = ObjectId(match.group(3))
354371
return anchor
355-
raise ValueError(f"{ref_id}] is not a valid reference!")
372+
raise ValueError(f"[{ref_id}] is not a valid reference!")
356373

357374
####################################################
358375
# QUERY OPERATIONS #
@@ -857,9 +874,61 @@ def delete(self, bulk_write: BulkWrite) -> None:
857874

858875

859876
@dataclass(eq=False, repr=False, kw_only=True)
860-
class ObjectAnchor(BaseAnchor, Anchor): # type: ignore[misc]
877+
class ObjectAnchor(BaseAnchor, _ObjectAnchor): # type: ignore[misc]
861878
"""Object Anchor."""
862879

880+
architype: "ObjectArchitype"
881+
882+
class Collection(BaseCollection["ObjectAnchor"]):
883+
"""ObjectAnchor collection interface."""
884+
885+
__collection__: str | None = "object"
886+
__default_indexes__: list[dict] = [
887+
{"keys": [("_id", ASCENDING), ("name", ASCENDING), ("root", ASCENDING)]}
888+
]
889+
890+
@classmethod
891+
def __document__(cls, doc: Mapping[str, Any]) -> "ObjectAnchor":
892+
"""Parse document to NodeAnchor."""
893+
doc = cast(dict, doc)
894+
895+
architype = architype_to_dataclass(
896+
ObjectArchitype.__get_class__(doc.get("name") or "Root"),
897+
doc.pop("architype"),
898+
)
899+
anchor = ObjectAnchor(
900+
architype=architype,
901+
id=doc.pop("_id"),
902+
access=Permission.deserialize(doc.pop("access")),
903+
state=AnchorState(connected=True),
904+
persistent=True,
905+
**doc,
906+
)
907+
architype.__jac__ = anchor
908+
anchor.sync_hash()
909+
return anchor
910+
911+
@classmethod
912+
def ref(cls, ref_id: str) -> "ObjectAnchor":
913+
"""Return NodeAnchor instance if existing."""
914+
if match := NODE_ID_REGEX.search(ref_id):
915+
anchor = object.__new__(cls)
916+
anchor.name = str(match.group(1))
917+
anchor.id = ObjectId(match.group(2))
918+
return anchor
919+
raise ValueError(f"[{ref_id}] is not a valid reference!")
920+
921+
def insert(
922+
self,
923+
bulk_write: BulkWrite,
924+
) -> None:
925+
"""Append Insert Query."""
926+
bulk_write.operations[ObjectAnchor].append(InsertOne(self.serialize()))
927+
928+
def delete(self, bulk_write: BulkWrite) -> None:
929+
"""Append Delete Query."""
930+
bulk_write.del_node(self.id)
931+
863932

864933
class BaseArchitype:
865934
"""Architype Protocol."""
@@ -960,7 +1029,7 @@ def __ref_cls__(cls) -> str:
9601029
return f"w:{cls.__name__}"
9611030

9621031

963-
class ObjectArchitype(BaseArchitype, Architype):
1032+
class ObjectArchitype(BaseArchitype, _ObjectArchitype):
9641033
"""Object Architype Protocol."""
9651034

9661035
__jac__: ObjectAnchor

jac-cloud/jac_cloud/core/memory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
BulkWrite,
2020
EdgeAnchor,
2121
NodeAnchor,
22+
ObjectAnchor,
2223
Root,
2324
WalkerAnchor,
2425
)
@@ -154,6 +155,8 @@ def get_bulk_write(self) -> BulkWrite:
154155
bulk_write.del_edge(anchor.id)
155156
case WalkerAnchor():
156157
bulk_write.del_walker(anchor.id)
158+
case ObjectAnchor():
159+
bulk_write.del_object(anchor.id)
157160
case _:
158161
pass
159162

jac-cloud/jac_cloud/plugin/jaseci.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
from fastapi.responses import ORJSONResponse
2424

2525
from jaclang.compiler.constant import EdgeDir
26-
from jaclang.plugin.default import JacFeatureImpl, hookimpl
26+
from jaclang.plugin.default import (
27+
JacCallableImplementation as _JacCallableImplementation,
28+
JacFeatureImpl,
29+
hookimpl,
30+
)
2731
from jaclang.plugin.feature import JacFeature as Jac
28-
from jaclang.runtimelib.architype import DSFunc
32+
from jaclang.runtimelib.architype import Architype, DSFunc
2933

3034
from orjson import loads
3135

@@ -37,7 +41,6 @@
3741
AccessLevel,
3842
Anchor,
3943
AnchorState,
40-
Architype,
4144
BaseAnchor,
4245
EdgeAnchor,
4346
EdgeArchitype,
@@ -305,6 +308,22 @@ class DefaultSpecs:
305308
private: bool = False
306309

307310

311+
class JacCallableImplementation:
312+
"""Callable Implementations."""
313+
314+
@staticmethod
315+
def get_object(id: str) -> Architype | None:
316+
"""Get object by id."""
317+
if not FastAPI.is_enabled():
318+
return _JacCallableImplementation.get_object(id=id)
319+
320+
with suppress(ValueError):
321+
if isinstance(architype := BaseAnchor.ref(id).architype, Architype):
322+
return architype
323+
324+
return None
325+
326+
308327
class JacAccessValidationPlugin:
309328
"""Jac Access Validation Implementations."""
310329

@@ -729,16 +748,9 @@ def builder(source: NodeAnchor, target: NodeAnchor) -> EdgeArchitype:
729748

730749
@staticmethod
731750
@hookimpl
732-
def get_object(id: str) -> Architype | None:
733-
"""Get object via reference id."""
734-
if not FastAPI.is_enabled():
735-
return JacFeatureImpl.get_object(id=id)
736-
737-
with suppress(ValueError):
738-
if isinstance(architype := BaseAnchor.ref(id).architype, Architype):
739-
return architype
740-
741-
return None
751+
def get_object_func() -> Callable[[str], Architype | None]:
752+
"""Get object by id func."""
753+
return JacCallableImplementation.get_object
742754

743755
@staticmethod
744756
@hookimpl

0 commit comments

Comments
 (0)