Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE]: Savable Object #1442

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions jac-cloud/jac_cloud/core/architype.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
Access as _Access,
AccessLevel,
Anchor,
Architype,
DSFunc,
EdgeAnchor as _EdgeAnchor,
EdgeArchitype as _EdgeArchitype,
NodeAnchor as _NodeAnchor,
NodeArchitype as _NodeArchitype,
ObjectAnchor as _ObjectAnchor,
ObjectArchitype as _ObjectArchitype,
Permission as _Permission,
TANCH,
WalkerAnchor as _WalkerAnchor,
Expand All @@ -53,10 +54,11 @@
from ..jaseci.utils import logger

MANUAL_SAVE = getenv("MANUAL_SAVE")
GENERIC_ID_REGEX = compile(r"^(n|e|w):([^:]*):([a-f\d]{24})$", IGNORECASE)
GENERIC_ID_REGEX = compile(r"^(n|e|w|o):([^:]*):([a-f\d]{24})$", IGNORECASE)
NODE_ID_REGEX = compile(r"^n:([^:]*):([a-f\d]{24})$", IGNORECASE)
EDGE_ID_REGEX = compile(r"^e:([^:]*):([a-f\d]{24})$", IGNORECASE)
WALKER_ID_REGEX = compile(r"^w:([^:]*):([a-f\d]{24})$", IGNORECASE)
OBJECT_ID_REGEX = compile(r"^o:([^:]*):([a-f\d]{24})$", IGNORECASE)
T = TypeVar("T")
TBA = TypeVar("TBA", bound="BaseArchitype")

Expand Down Expand Up @@ -167,12 +169,14 @@ class BulkWrite:
NodeAnchor: [],
EdgeAnchor: [],
WalkerAnchor: [],
ObjectAnchor: [],
}
)

del_ops_nodes: list[ObjectId] = field(default_factory=list)
del_ops_edges: list[ObjectId] = field(default_factory=list)
del_ops_walker: list[ObjectId] = field(default_factory=list)
del_ops_object: list[ObjectId] = field(default_factory=list)

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

self.del_ops_walker.append(id)

def del_object(self, id: ObjectId) -> None:
"""Add walker to delete many operations."""
if not self.del_ops_object:
self.operations[ObjectAnchor].append(
DeleteMany({"_id": {"$in": self.del_ops_object}})
)

self.del_ops_object.append(id)

@property
def has_operations(self) -> bool:
"""Check if has operations."""
Expand Down Expand Up @@ -244,6 +257,8 @@ def execute(self, session: ClientSession) -> None:
EdgeAnchor.Collection.bulk_write(edge_operation, False, session)
if walker_operation := self.operations[WalkerAnchor]:
WalkerAnchor.Collection.bulk_write(walker_operation, False, session)
if object_operation := self.operations[ObjectAnchor]:
ObjectAnchor.Collection.bulk_write(object_operation, False, session)
self.commit(session)
break
except (ConnectionFailure, OperationFailure) as ex:
Expand Down Expand Up @@ -346,13 +361,15 @@ def ref(ref_id: str) -> "BaseAnchor | Anchor":
cls = EdgeAnchor
case "w":
cls = WalkerAnchor
case "o":
cls = ObjectAnchor
case _:
raise ValueError(f"{ref_id}] is not a valid reference!")
raise ValueError(f"[{ref_id}] is not a valid reference!")
anchor = object.__new__(cls)
anchor.name = str(match.group(2))
anchor.id = ObjectId(match.group(3))
return anchor
raise ValueError(f"{ref_id}] is not a valid reference!")
raise ValueError(f"[{ref_id}] is not a valid reference!")

####################################################
# QUERY OPERATIONS #
Expand Down Expand Up @@ -857,9 +874,61 @@ def delete(self, bulk_write: BulkWrite) -> None:


@dataclass(eq=False, repr=False, kw_only=True)
class ObjectAnchor(BaseAnchor, Anchor): # type: ignore[misc]
class ObjectAnchor(BaseAnchor, _ObjectAnchor): # type: ignore[misc]
"""Object Anchor."""

architype: "ObjectArchitype"

class Collection(BaseCollection["ObjectAnchor"]):
"""ObjectAnchor collection interface."""

__collection__: str | None = "object"
__default_indexes__: list[dict] = [
{"keys": [("_id", ASCENDING), ("name", ASCENDING), ("root", ASCENDING)]}
]

@classmethod
def __document__(cls, doc: Mapping[str, Any]) -> "ObjectAnchor":
"""Parse document to NodeAnchor."""
doc = cast(dict, doc)

architype = architype_to_dataclass(
ObjectArchitype.__get_class__(doc.get("name") or "Root"),
doc.pop("architype"),
)
anchor = ObjectAnchor(
architype=architype,
id=doc.pop("_id"),
access=Permission.deserialize(doc.pop("access")),
state=AnchorState(connected=True),
persistent=True,
**doc,
)
architype.__jac__ = anchor
anchor.sync_hash()
return anchor

@classmethod
def ref(cls, ref_id: str) -> "ObjectAnchor":
"""Return NodeAnchor instance if existing."""
if match := NODE_ID_REGEX.search(ref_id):
anchor = object.__new__(cls)
anchor.name = str(match.group(1))
anchor.id = ObjectId(match.group(2))
return anchor
raise ValueError(f"[{ref_id}] is not a valid reference!")

def insert(
self,
bulk_write: BulkWrite,
) -> None:
"""Append Insert Query."""
bulk_write.operations[ObjectAnchor].append(InsertOne(self.serialize()))

def delete(self, bulk_write: BulkWrite) -> None:
"""Append Delete Query."""
bulk_write.del_node(self.id)


class BaseArchitype:
"""Architype Protocol."""
Expand Down Expand Up @@ -960,7 +1029,7 @@ def __ref_cls__(cls) -> str:
return f"w:{cls.__name__}"


class ObjectArchitype(BaseArchitype, Architype):
class ObjectArchitype(BaseArchitype, _ObjectArchitype):
"""Object Architype Protocol."""

__jac__: ObjectAnchor
Expand Down
3 changes: 3 additions & 0 deletions jac-cloud/jac_cloud/core/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BulkWrite,
EdgeAnchor,
NodeAnchor,
ObjectAnchor,
Root,
WalkerAnchor,
)
Expand Down Expand Up @@ -154,6 +155,8 @@ def get_bulk_write(self) -> BulkWrite:
bulk_write.del_edge(anchor.id)
case WalkerAnchor():
bulk_write.del_walker(anchor.id)
case ObjectAnchor():
bulk_write.del_object(anchor.id)
case _:
pass

Expand Down
38 changes: 25 additions & 13 deletions jac-cloud/jac_cloud/plugin/jaseci.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
from fastapi.responses import ORJSONResponse

from jaclang.compiler.constant import EdgeDir
from jaclang.plugin.default import JacFeatureImpl, hookimpl
from jaclang.plugin.default import (
JacCallableImplementation as _JacCallableImplementation,
JacFeatureImpl,
hookimpl,
)
from jaclang.plugin.feature import JacFeature as Jac
from jaclang.runtimelib.architype import DSFunc
from jaclang.runtimelib.architype import Architype, DSFunc

from orjson import loads

Expand All @@ -37,7 +41,6 @@
AccessLevel,
Anchor,
AnchorState,
Architype,
BaseAnchor,
EdgeAnchor,
EdgeArchitype,
Expand Down Expand Up @@ -305,6 +308,22 @@ class DefaultSpecs:
private: bool = False


class JacCallableImplementation:
"""Callable Implementations."""

@staticmethod
def get_object(id: str) -> Architype | None:
"""Get object by id."""
if not FastAPI.is_enabled():
return _JacCallableImplementation.get_object(id=id)

with suppress(ValueError):
if isinstance(architype := BaseAnchor.ref(id).architype, Architype):
return architype

return None


class JacAccessValidationPlugin:
"""Jac Access Validation Implementations."""

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

@staticmethod
@hookimpl
def get_object(id: str) -> Architype | None:
"""Get object via reference id."""
if not FastAPI.is_enabled():
return JacFeatureImpl.get_object(id=id)

with suppress(ValueError):
if isinstance(architype := BaseAnchor.ref(id).architype, Architype):
return architype

return None
def get_object_func() -> Callable[[str], Architype | None]:
"""Get object by id func."""
return JacCallableImplementation.get_object

@staticmethod
@hookimpl
Expand Down
Loading
Loading