Python
El paquete namidb de Python es un wrapper de PyO3 / maturin sobre el
motor en Rust. El trabajo pesado pasa en Rust; la superficie Python es
delgada y explícita.
Consulta Instalación
para la matriz completa de instalación y el requisito de
pyarrow >= 14.
Client(uri)
import namidb
client = namidb.Client("memory://demo")La URI selecciona el backend (consulta Backends de almacenamiento para la gramática completa). La primera llamada por proceso arranca un runtime de tokio que es propiedad del cliente.
El parseo de URI delega en la misma implementación Rust que usan la
CLI y el servidor. URIs mal formadas lanzan PyValueError; los
fallos de inicialización del backend lanzan PyRuntimeError.
Dos caminos de escritura
Cypher (auto-commit)
client.cypher("CREATE (a:Person {name: 'Alice', age: 30})")client.cypher("CREATE (a:Person {name: 'Bob', age: 25})")
result = client.cypher( "MATCH (p:Person) WHERE p.age > $min RETURN p.name AS name, p.age AS age", params={"min": 26},)
print(result.columns) # ['name', 'age']print(len(result)) # 1print(result.first()) # {'name': 'Alice', 'age': 30}for row in result.rows(): print(row)Las escrituras Cypher (CREATE, SET, DELETE, MERGE, REMOVE)
se persisten antes de que retorne cypher() — el executor llama
commit_batch() al final de cada plan de escritura. Todavía conviene
llamar client.flush() periódicamente para empujar el memtable a
SSTs L0, pero la durabilidad ya está cubierta.
API de staging tipada
import uuidimport namidb
client = namidb.Client("memory://demo")
alice = str(uuid.uuid7())bob = str(uuid.uuid7())
client.upsert_node("Person", alice, {"name": "Alice", "age": 30})client.upsert_node("Person", bob, {"name": "Bob"})client.upsert_edge("KNOWS", alice, bob, {"since": 2020})client.commit() # WAL + manifest CASclient.flush() # memtable -> L0 SSTsLa API de staging acumula mutaciones en el batch actual y espera un
client.commit() explícito. Pásalo junto con client.flush()
cuando quieras los SSTs L0 en disco / en el bucket de inmediato.
Los tombstones usan la misma forma: client.tombstone_node(label, id),
client.tombstone_edge(edge_type, src, dst).
Inserts en bulk
Para miles de nodos / aristas en un round-trip, prefiere
merge_nodes / merge_edges — agrupan muchas escrituras detrás de un
solo round-trip de runtime de tokio + mutex:
client.merge_nodes( "Person", [{"id": str(uuid.uuid4()), "name": f"p{i}", "age": 20 + i} for i in range(10_000)],)client.merge_edges( "KNOWS", [ {"src": "uuid-a", "dst": "uuid-b", "since": 2020}, {"src": "uuid-b", "dst": "uuid-c", "since": 2021}, ],)client.commit()client.flush()Cada fila de nodo necesita un id (UUID string) más properties
arbitrarias. Cada fila de arista necesita src + dst (UUID strings)
más properties arbitrarias. merge_nodes / merge_edges se quedan
en stage en el batch actual — llama commit() para hacerlas
persistentes.
Async (acypher)
import asyncioimport namidb
async def main() -> None: client = namidb.Client("memory://demo") await client.acypher("CREATE (p:Person {name: 'Alice'})") result = await client.acypher( "MATCH (p:Person {name: $name}) RETURN p.name AS name", params={"name": "Alice"}, ) print(result.rows())
asyncio.run(main())acypher se monta sobre el bridge tokio de pyo3-async-runtimes.
Cada llamada corre sobre el mismo runtime multi-thread de tokio que
respalda la API síncrona, así que mezclar las dos desde un solo
Client está bien.
Formatos de resultado
QueryResult expone las mismas filas en varias formas. pyarrow >= 14
es una dependencia obligatoria, así que to_arrow() siempre está
disponible:
result = client.cypher( "MATCH (p:Person) RETURN p.name AS name, p.age AS age ORDER BY p.age DESC")
result.rows() # list[dict] — fácil para resultados chicostable = result.to_arrow() # pyarrow.Tabledf = result.to_pandas() # pandas.DataFrame (necesita el extra de pandas)pl_df = result.to_polars() # polars.DataFrame (necesita el extra de polars)Llamar to_polars() sin el extra de polars lanza un ImportError
claro que apunta al comando de instalación.
El orden de columnas sigue la proyección del RETURN del plan
parseado, así que RETURN p.name AS name, p.age AS age siempre te da
columnas ["name", "age"] — aun cuando no matchee nada.
Para escaneos de toda una label puedes saltarte el round-trip de Cypher:
table = client.scan_label_arrow("Person")# Columnas: id, label, lsn, schema_version, después la unión de las# claves de property a lo largo de las vistas escaneadas (claves# faltantes se rellenan con None).API directa
Para lookups que no justifican un round-trip de Cypher:
client.lookup_node("Person", alice)# {'id': '...', 'label': 'Person', 'lsn': 1, 'schema_version': 0,# 'properties': {'name': 'Alice', 'age': 30}}
client.scan_label("Person")client.out_edges("KNOWS", alice)client.cache_stats() # {hit, miss, evict, ...} del SstCache compartidoMapeo de tipos (Cypher ↔ Python)
Cypher RuntimeValue | Tipo Python |
|---|---|
Null | None |
Bool | bool |
Integer | int |
Float | float |
String | str |
Bytes | bytes |
Vector(Vec<f32>) | list[float] |
List | list |
Map | dict[str, ...] |
Date | datetime.date |
DateTime (UTC, μs) | datetime.datetime (tz=UTC) |
Node | {"_kind": "node", "id", "label", "properties"} |
Rel | {"_kind": "rel", "edge_type", "src", "dst", "properties"} |
Path | list[Node|Rel] alternando |
bool se chequea antes que int para que True / False no se
conviertan silenciosamente a Integer(1) / Integer(0).
Siguientes pasos
- Leer datos — cláusulas de lectura.
- Escribir datos — cláusulas de escritura y auto-commit.
- Embedded (Python) — cuándo y por qué embeber vs. hablar con el servidor HTTP.
- API HTTP — hablar con un
namidb-serverremoto desde cualquier lugar.