Skip to content

Commit e1c2e3c

Browse files
committed
feat: add coordinode-embedded in-process Python package
- Add coordinode-rs git submodule pinned to v0.3.6 - Implement LocalClient PyO3 bindings over coordinode-embed Rust crate - Support :memory: (via tempdir) and persistent storage modes - Full Value type conversion: all 12 property types including Document - cypher(query, params=None) API compatible with CoordinodeClient - maturin build system with 5-platform release matrix in CI - publish-pypi-embedded job for PyPI publishing on tag push
1 parent 14a189c commit e1c2e3c

11 files changed

Lines changed: 567 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ jobs:
4545
- name: Unit tests
4646
run: uv run pytest tests/unit/ -v
4747

48+
build-embedded:
49+
name: Build embedded (CI check)
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
53+
with:
54+
submodules: recursive
55+
56+
- name: Build (maturin / Linux x86_64)
57+
uses: PyO3/maturin-action@b03994fa3b4aa2d8c697185e0e66d1fa2041bfe2 # v1.47.0
58+
with:
59+
command: build
60+
args: >-
61+
--manifest-path coordinode-embedded/Cargo.toml
62+
--out /tmp/embedded-dist
63+
manylinux: manylinux_2_28
64+
4865
test-integration:
4966
name: Integration tests
5067
runs-on: ubuntu-latest

.github/workflows/release.yml

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ on:
1313
permissions:
1414
contents: write
1515

16+
# ── Pure-Python packages (coordinode, langchain-coordinode, llama-index) ──────
17+
1618
jobs:
1719
build:
1820
name: Build ${{ matrix.package.name }}
@@ -57,10 +59,74 @@ jobs:
5759
path: ${{ matrix.package.path }}/dist/
5860
retention-days: 1
5961

62+
# ── coordinode-embedded: compiled Rust extension, one wheel per platform ──────
63+
64+
build-embedded:
65+
name: Build embedded / ${{ matrix.target }} (${{ matrix.os }})
66+
strategy:
67+
fail-fast: false
68+
matrix:
69+
include:
70+
# Linux x86_64 — most common server target
71+
- os: ubuntu-latest
72+
target: x86_64
73+
manylinux: manylinux_2_28
74+
75+
# Linux aarch64 — AWS Graviton, Raspberry Pi, etc.
76+
- os: ubuntu-latest
77+
target: aarch64
78+
manylinux: manylinux_2_28
79+
80+
# macOS Apple Silicon (M1/M2/M3)
81+
- os: macos-latest
82+
target: aarch64
83+
84+
# macOS Intel
85+
- os: macos-13
86+
target: x86_64
87+
88+
# Windows x86_64
89+
- os: windows-latest
90+
target: x86_64
91+
92+
runs-on: ${{ matrix.os }}
93+
steps:
94+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
95+
with:
96+
submodules: recursive
97+
fetch-depth: 0
98+
99+
# Pin the coordinode-rs submodule to the tag that matches this release.
100+
# The submodule SHA in git already points to the right commit; this step
101+
# ensures the tag annotation is present for version detection.
102+
- name: Verify coordinode-rs submodule tag
103+
run: git -C coordinode-rs describe --tags --exact-match HEAD || true
104+
105+
- name: Build wheels
106+
uses: PyO3/maturin-action@b03994fa3b4aa2d8c697185e0e66d1fa2041bfe2 # v1.47.0
107+
with:
108+
command: build
109+
args: >-
110+
--release
111+
--strip
112+
--manifest-path coordinode-embedded/Cargo.toml
113+
--out dist
114+
manylinux: ${{ matrix.manylinux || 'auto' }}
115+
target: ${{ matrix.target }}
116+
117+
- name: Upload wheels
118+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
119+
with:
120+
name: dist-coordinode-embedded-${{ matrix.target }}-${{ matrix.os }}
121+
path: dist/
122+
retention-days: 1
123+
124+
# ── Publish all packages to PyPI ─────────────────────────────────────────────
125+
60126
publish-pypi:
61127
name: Publish to PyPI
62128
runs-on: ubuntu-latest
63-
needs: build
129+
needs: [build, build-embedded]
64130
environment: pypi
65131
permissions:
66132
id-token: write
@@ -80,10 +146,30 @@ jobs:
80146
with:
81147
packages-dir: dist/
82148

149+
publish-pypi-embedded:
150+
name: Publish coordinode-embedded to PyPI
151+
runs-on: ubuntu-latest
152+
needs: build-embedded
153+
environment: pypi
154+
permissions:
155+
id-token: write
156+
steps:
157+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
158+
with:
159+
pattern: dist-coordinode-embedded-*
160+
merge-multiple: true
161+
path: dist/
162+
163+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
164+
with:
165+
packages-dir: dist/
166+
167+
# ── GitHub Release — attach all wheels ───────────────────────────────────────
168+
83169
github-release:
84170
name: Create GitHub Release
85171
runs-on: ubuntu-latest
86-
needs: build
172+
needs: [build, build-embedded]
87173
permissions:
88174
contents: write
89175
steps:

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
[submodule "proto"]
22
path = proto
33
url = https://github.com/structured-world/coordinode-proto-ce.git
4+
[submodule "coordinode-rs"]
5+
path = coordinode-rs
6+
url = https://github.com/structured-world/coordinode.git
7+
shallow = true

coordinode-embedded/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
Cargo.lock

coordinode-embedded/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "coordinode-embedded"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.90"
6+
description = "CoordiNode embedded Python bindings — in-process graph database, no server required"
7+
license = "Apache-2.0"
8+
repository = "https://github.com/structured-world/coordinode-python"
9+
readme = "README.md"
10+
11+
[lib]
12+
name = "_coordinode_embedded"
13+
crate-type = ["cdylib"]
14+
15+
[dependencies]
16+
pyo3 = { version = "0.23", features = ["extension-module"] }
17+
coordinode-embed = { path = "../coordinode-rs/crates/coordinode-embed" }
18+
coordinode-core = { path = "../coordinode-rs/crates/coordinode-core" }
19+
rmpv = "1"
20+
tempfile = "3"
21+
22+
[profile.release]
23+
lto = "fat"
24+
codegen-units = 1
25+
strip = true

coordinode-embedded/pyproject.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[build-system]
2+
requires = ["maturin>=1.7,<2"]
3+
build-backend = "maturin"
4+
5+
[project]
6+
name = "coordinode-embedded"
7+
description = "CoordiNode embedded Python bindings — in-process graph database, no server required"
8+
readme = "README.md"
9+
license = { text = "Apache-2.0" }
10+
requires-python = ">=3.11"
11+
keywords = ["graph", "database", "cypher", "embedded", "knowledge-graph"]
12+
classifiers = [
13+
"Development Status :: 4 - Beta",
14+
"Intended Audience :: Developers",
15+
"License :: OSI Approved :: Apache Software License",
16+
"Programming Language :: Python :: 3",
17+
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
20+
"Programming Language :: Rust",
21+
"Topic :: Database",
22+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
23+
]
24+
dynamic = ["version"]
25+
26+
[project.urls]
27+
Homepage = "https://coordinode.dev"
28+
Repository = "https://github.com/structured-world/coordinode-python"
29+
Documentation = "https://docs.coordinode.dev"
30+
31+
[tool.maturin]
32+
python-source = "python"
33+
module-name = "coordinode_embedded._coordinode_embedded"
34+
features = ["pyo3/extension-module"]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
coordinode-embedded — CoordiNode in-process Python bindings.
3+
4+
Run the full CoordiNode graph engine in-process: no server, no Docker,
5+
no network. Compatible with ``CoordinodeClient`` — same ``.cypher()``
6+
API, drop-in for notebooks and local development.
7+
8+
Example::
9+
10+
from coordinode_embedded import LocalClient
11+
12+
# In-memory (Google Colab, unit tests, quick scripts)
13+
with LocalClient(":memory:") as db:
14+
db.cypher("CREATE (n:Person {name: $name})", {"name": "Alice"})
15+
rows = db.cypher("MATCH (n:Person) RETURN n.name AS name")
16+
print(rows) # [{"name": "Alice"}]
17+
18+
# Persistent storage
19+
db = LocalClient("/path/to/data")
20+
db.cypher("MERGE (n:Company {name: 'Acme'})")
21+
db.close()
22+
"""
23+
24+
from ._coordinode_embedded import LocalClient
25+
26+
__all__ = ["LocalClient"]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Type stubs for the compiled Rust extension module."""
2+
3+
from typing import Any
4+
5+
class LocalClient:
6+
"""In-process CoordiNode database — no server, no Docker required.
7+
8+
Compatible with ``CoordinodeClient``: same ``.cypher()`` method returns
9+
``list[dict]``. Drop-in for local development and notebook environments
10+
(Google Colab, Jupyter).
11+
12+
Args:
13+
path: Filesystem path for persistent storage, or ``":memory:"`` for an
14+
in-memory database that is discarded on close.
15+
16+
Example::
17+
18+
from coordinode_embedded import LocalClient
19+
20+
with LocalClient(":memory:") as db:
21+
db.cypher("CREATE (n:Person {name: $name})", {"name": "Alice"})
22+
rows = db.cypher("MATCH (n:Person) RETURN n.name AS name")
23+
"""
24+
25+
def __init__(self, path: str) -> None: ...
26+
27+
def cypher(
28+
self,
29+
query: str,
30+
params: dict[str, Any] | None = None,
31+
) -> list[dict[str, Any]]:
32+
"""Execute a Cypher query and return results as a list of dicts.
33+
34+
Args:
35+
query: Cypher query string.
36+
params: Optional dict of query parameters (``$name`` style).
37+
38+
Returns:
39+
``list[dict[str, Any]]`` — one dict per result row.
40+
"""
41+
...
42+
43+
def close(self) -> None:
44+
"""Close the database and release all resources.
45+
46+
After calling ``close()``, any further method calls raise ``RuntimeError``.
47+
In-memory databases discard all data on close.
48+
"""
49+
...
50+
51+
def __enter__(self) -> "LocalClient": ...
52+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool: ...
53+
def __repr__(self) -> str: ...

coordinode-embedded/python/coordinode_embedded/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)