PythonX runtime owner for the embedded Gralkor stack.
Responsibilities, all in init/1:
Reap redislite orphans.
falkordblite(loaded into PythonX in this BEAM) spawns aredis-servergrandchild. A hard BEAM SIGKILL leaves it orphaned. SIGKILL anything matchingredislite/bin/redis-serverbefore we boot — safe because this runs before our own Python init, so anything matching is by definition not ours-yet, and the path is unique to falkordblite.Materialise the venv + initialise the interpreter via
Pythonx.uv_init/2from the jido_gralkor-owned@pyproject_toml. This is what makes the embedded Python stack self-contained: the consumer (e.g. susu) configures nothing about Python — the graphiti-core pin is jido_gralkor's private detail. Guarded against re-init (the Pythonx NIF throws "already been initialized" on a second call) so multiple boots in one VM — as functional-test modules do — are safe.Smoke-import
graphiti_corethrough PythonX so any venv / import failure surfaces at boot rather than on the first real call.
See ex-python-runtime in gralkor/TEST_TREES.md.
Summary
Functions
Returns a specification to start this module under a supervisor.
Materialise the uv-managed venv and initialise the PythonX interpreter from
the jido_gralkor-owned @pyproject_toml. Idempotent within a VM: a flag in
:persistent_term short-circuits the second call so we never hit the NIF's
"already been initialized" guard when more than one Gralkor.Python boots.
Spin up a daemon-thread asyncio event loop and stash it on asyncio as
_gralkor_loop plus a _gralkor_run(coro) helper that submits onto it.
SIGKILL every pid the listing function returns. Pure plumbing — accepts injected list/kill functions so the unit test doesn't have to spawn real redis processes.
Try to import graphiti_core via Pythonx; surface any failure as
{:error, _}.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec ensure_initialised() :: :ok | {:error, term()}
Materialise the uv-managed venv and initialise the PythonX interpreter from
the jido_gralkor-owned @pyproject_toml. Idempotent within a VM: a flag in
:persistent_term short-circuits the second call so we never hit the NIF's
"already been initialized" guard when more than one Gralkor.Python boots.
@spec install_async_runtime() :: :ok | {:error, term()}
Spin up a daemon-thread asyncio event loop and stash it on asyncio as
_gralkor_loop plus a _gralkor_run(coro) helper that submits onto it.
Must run once per Pythonx interpreter, before any code that calls into
graphiti via asyncio._gralkor_run. Idempotent — the second call is a
no-op.
Why: Pythonx.eval creates a fresh event loop per asyncio.run call.
AsyncFalkorDB (and any redis-async connection) binds its connections to
the loop they were created on; reusing them on a different loop raises
"Future attached to a different loop". The spike measured the alternative
pattern (Step 6 in pythonx-spike/spike.exs) at ~56µs per call vs ~112µs
for asyncio.run — and, crucially, it shares one loop across all calls
so connection reuse works.
SIGKILL every pid the listing function returns. Pure plumbing — accepts injected list/kill functions so the unit test doesn't have to spawn real redis processes.
@spec smoke_import_graphiti() :: :ok | {:error, term()}
Try to import graphiti_core via Pythonx; surface any failure as
{:error, _}.