Record Wrappers (Document, Vertex, Edge)¶
The Python API provides wrapper classes for database records: Document, Vertex, and
Edge.
Compatibility-oriented API
Prefer SQL/OpenCypher via db.command(...) and db.query(...) for normal schema,
CRUD, and graph workflows. This page documents the wrapper layer for compatibility,
targeted record manipulation, and wrapper-specific traversal helpers.
Overview¶
| Class | Purpose | Usage |
|---|---|---|
Document |
Base wrapper for all records (documents, vertices, edges) | Property access, modification, deletion |
Vertex |
Wrapper for graph vertices | Creating edges, traversal |
Edge |
Wrapper for graph edges | Accessing source/target vertices |
A record read from a database keeps that Database alive, and reading it after the
database was closed raises ArcadeDBError ("Database is closed"). The engine loads a
record's properties lazily from the open database, so there is nothing to return once
it is closed. See Database.close().
Document Wrapper¶
The Document class is the base wrapper for all record types. Use it for documents and
as the base class for Vertex and Edge.
Creating Documents¶
For application code, prefer INSERT INTO ... statements. Use db.new_document(...)
when you explicitly need the wrapper object in hand.
import arcadedb_embedded as arcadedb
from arcadedb_embedded import Document, Vertex, Edge
with arcadedb.create_database("./mydb") as db:
db.command("sql", "CREATE DOCUMENT TYPE Note")
# Create a document
with db.transaction():
doc = db.new_document("Note")
doc.set("title", "My Note")
doc.set("content", "Important information")
doc.save()
# Get the RID for later retrieval
doc_id = str(doc.get_identity())
print(f"Created document: {doc_id}")
Properties and Methods¶
set(name, value) -> Document¶
Set a property on the document. Returns self for chaining.
doc.set("name", "Alice")
doc.set("age", 30)
doc.set("active", True)
# Chaining
doc.set("name", "Bob").set("age", 25).save()
get(name, convert_types: bool = True) -> Any¶
Get a property value. With convert_types=True (default) Java types are converted to
Python types; pass False to get the raw Java-backed value (equivalent to get_raw).
name = doc.get("name")
age = doc.get("age")
email = doc.get("email") # Returns None if not found
email = doc.get("email") or "unknown" # Use default pattern
get_raw(name) -> Any¶
Get a property value without Java-to-Python conversion. Returns the raw Java-backed
value, or None if the property doesn't exist. Equivalent to
get(name, convert_types=False).
get_property_names() -> List[str]¶
Get all property names on the document.
props = doc.get_property_names()
print(f"Properties: {props}")
# Output: Properties: ['name', 'age', 'active']
has_property(name) -> bool¶
Check if a property exists.
save() -> Document¶
Save changes to the database. Returns self.
Set every property of a new record before its first save(). A record saved and then
changed in the same transaction is written a second time, and the second write grows it
inside its page: at 200,000 new vertices, 1,000 per transaction, saving each vertex once
measured 110,000 to 120,000 per second and saving it, setting one more property, and saving again
about 52,000 (ArcadeDB #8735).
Call save() after the last change to a record. A change made after the record was saved,
and not saved again, is not written at commit: in one transaction,
d.set("k", 1); d.save(); d.set("k", 2) commits k = 1, and an index on k agrees (checked
on 26.10.1-SNAPSHOT). Upstream treats changing a record after its last save() as unsupported;
the engine and its SQL and Cypher executors always save after the last change (ArcadeDB
#8989).
delete() -> None¶
Delete the document from the database.
⚠️ Important Limitation: Call it on a wrapper from lookup_by_rid() or on a newly
created object. Iterating a query yields Result rows, which have no delete(); use SQL
DELETE for query results.
# ✅ Works on fresh lookup
with db.transaction():
doc = db.lookup_by_rid("#1:0")
doc.delete()
# ✅ Works on newly created
with db.transaction():
doc = db.new_document("Note")
doc.set("title", "Test")
doc.save()
doc.delete()
# ❌ Doesn't work on query results
results = db.query("sql", "SELECT FROM Note WHERE title = 'Test'")
for row in results:
row.delete() # AttributeError: a query row is a Result, not a Document
# ✅ Use SQL DELETE instead
with db.transaction():
db.command("sql", "DELETE FROM Note WHERE title = 'Test'")
to_dict(convert_types: bool = True) -> dict¶
Convert the document to a Python dictionary of its properties (metadata like RID/type
is not included). Use get_rid() for the record ID if needed. Pass
convert_types=False to keep raw Java-backed values.
Performance note: to_dict() eagerly converts the full document into Python
data. For large scans or repeated wrapper access, prefer get() when you only need
specific fields.
doc_dict = doc.to_dict()
print(doc_dict)
# Output: {'name': 'Alice', 'age': 30, 'active': True}
rid = doc.get_rid()
get_identity()¶
Get the record identity (the underlying Java RID object). For a string RID, use
get_rid() or wrap with str(...).
get_rid() -> str¶
Get the Record ID (RID) as a string.
get_type_name() -> str¶
Get the type name of the document.
get_java_document()¶
Expose the wrapped Java document object for low-level integrations. Use this only when you need the underlying Java API directly (camelCase JPype methods); normal code should stay on the Python wrapper.
modify() -> Document¶
Get a mutable version of the document. Records loaded from the database (by
lookup_by_rid() or through a query row's get_element()) are immutable until you call
it.
# A query row is a Result; get_element() returns the (immutable) record wrapper
immutable_doc = db.query("sql", "SELECT FROM Note LIMIT 1").first().get_element()
# Get mutable version for modification
with db.transaction():
mutable_doc = immutable_doc.modify()
mutable_doc.set("updated", True).save()
wrap(java_object) -> Document¶
Static method to wrap Java objects as Python wrappers. Automatically detects type.
from arcadedb_embedded import Document
# Wrap Java object and automatically detect type
wrapped = Document.wrap(java_object)
# Returns: Document, Vertex, or Edge depending on actual type
Vertex Wrapper¶
The Vertex class extends Document with graph-specific methods for wrapper-based edge
creation and traversal.
Creating Vertices¶
For most app code, prefer SQL/OpenCypher vertex creation and graph writes. Use
db.new_vertex(...) when you need direct wrapper manipulation.
db.command("sql", "CREATE VERTEX TYPE Person")
with db.transaction():
alice = db.new_vertex("Person")
alice.set("name", "Alice")
alice.set("age", 30)
alice.save()
print(f"Created vertex: {alice.get_identity()}")
Graph Methods¶
new_edge(label, target, **kwargs) -> Edge¶
Create an edge from this vertex to another vertex. Keyword arguments become edge properties.
with db.transaction():
alice = db.new_vertex("Person").set("name", "Alice").save()
bob = db.new_vertex("Person").set("name", "Bob").save()
# Create edge: new_edge saves it, so pass its properties here
edge = alice.new_edge("Knows", bob, since=2020)
get_out_edges(*labels) -> List[Edge]¶
Get outgoing edges from this vertex, optionally filtered by label.
# All outgoing edges
outgoing = alice.get_out_edges()
for edge in outgoing:
target = edge.get_in()
print(f"Alice -> {target.get('name')}")
# Filter by edge type
knows = alice.get_out_edges("Knows")
assert all(e.get_in().get("name") in {"Bob", "Carol"} for e in knows)
get_in_edges(*labels) -> List[Edge]¶
Get incoming edges to this vertex, optionally filtered by label. On an edge type declared UNIDIRECTIONAL this returns nothing: only the outgoing side is stored, so use a Cypher pattern or SQL MATCH, which see the incoming side.
incoming = alice.get_in_edges()
for edge in incoming:
source = edge.get_out()
print(f"{source.get('name')} -> Alice")
# Filter by edge type
knows_in = alice.get_in_edges("Knows")
get_both_edges(*labels) -> List[Edge]¶
Get both incoming and outgoing edges, optionally filtered by label.
all_edges = alice.get_both_edges()
print(f"Degree: {len(all_edges)}")
knows_edges = alice.get_both_edges("Knows")
Edge Wrapper¶
The Edge class represents a connection between vertices with optional properties.
Edge Properties¶
Edges have the same property methods as documents:
# new_edge saves the edge when it creates it: pass its properties as keyword
# arguments, because setting them afterwards and saving again writes it a second time
edge = alice.new_edge("Knows", bob, since=2020, strength=0.9)
print(edge.get("since")) # Output: 2020
edge.get_property_names() # ['since', 'strength']
Graph Methods¶
get_in() -> Vertex¶
Get the incoming (destination/head) vertex of the edge. Wraps getInVertex().
get_out() -> Vertex¶
Get the outgoing (source/tail) vertex of the edge. Wraps getOutVertex().
Best Practices¶
1. Prefer SQL/OpenCypher for set-based CRUD¶
with db.transaction():
db.command(
"sql",
"INSERT INTO Note SET title = ?, content = ?",
"My Note",
"Content",
)
results = db.query("sql", "SELECT FROM Note WHERE flagged = true")
Use wrappers when you specifically need object-style mutation, wrapper traversal, or a single looked-up record.
2. Always Save After Set¶
# ❌ Bad - changes not persisted
with db.transaction():
doc.set("name", "Alice")
# No save!
# ✅ Good
with db.transaction():
doc.set("name", "Alice")
doc.save()
3. Prefer SQL DELETE unless you already have a looked-up wrapper¶
# ❌ Query rows have no delete()
results = db.query("sql", "SELECT FROM Note")
for row in results:
row.delete() # AttributeError
# ✅ Wrapper delete on an explicitly looked-up record
with db.transaction():
doc = db.lookup_by_rid("#1:0")
doc.delete()
# ✅ Convert query results to RIDs, then delete via lookup
with db.transaction():
rids = [doc.get_rid() for doc in db.query("sql", "SELECT FROM Note WHERE flagged = true")]
for rid in rids:
db.lookup_by_rid(rid).delete()
# ✅ Default pattern for bulk or query-based delete
with db.transaction():
db.command("sql", "DELETE FROM Note WHERE flagged = true")