# `Gralkor.Python`
[🔗](https://github.com/elimydlarz/gralkor/blob/main/lib/gralkor/python.ex#L1)

PythonX runtime owner for the embedded Gralkor stack.

Two responsibilities, both in `init/1`:

  1. **Reap redislite orphans.** `falkordblite` (loaded into PythonX in this
     BEAM) spawns a `redis-server` grandchild. A hard BEAM SIGKILL leaves
     it orphaned. SIGKILL anything matching `redislite/bin/redis-server`
     before 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.

  2. **Smoke-import `graphiti_core`** through PythonX so any venv / import
     failure surfaces at boot rather than on the first real call.

  Pythonx's interpreter + venv materialisation are configured in
  `config/config.exs` via `:pythonx, :uv_init` and start automatically with
  the `:pythonx` OTP application; `Gralkor.Python` does not duplicate that
  work.

See `ex-python-runtime` in `gralkor/TEST_TREES.md`.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `install_async_runtime`

```elixir
@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.

# `reap_redislite_orphans`

```elixir
@spec reap_redislite_orphans((-&gt; [integer()]), (integer() -&gt; any())) ::
  :ok | {:error, term()}
```

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.

# `smoke_import_graphiti`

```elixir
@spec smoke_import_graphiti() :: :ok | {:error, term()}
```

Try to `import graphiti_core` via Pythonx; surface any failure as
`{:error, _}`.

# `start_link`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
