Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.python.host

THE door from the sandbox back into Vis, and the only one.

The embedded interpreter calls out through ONE function it was handed, so everything a block can ask the host for arrives here as two strings: the name of a tool and a JSON envelope of its arguments. What crosses is DATA - a tool takes plain values and answers plain values, because the boundary carries text and nothing else. A live object (an interpreter handle, a stream, a file) crossed it and never will; a handle the model holds is a PYTHON object built over calls that come back through here.

One interpreter serves every session in this process, so the envelope names the session that called (vis-python-runtime's install) and this registry answers per session: shell bound for two workspaces is two different functions under one name, and a session that never bound a name gets a refusal that says so instead of a neighbour's tool.

The host is bound ONCE per process. Binding is idempotent because the interpreter is: a second bind! would replace a live upcall stub while a block sits inside it.

THE door from the sandbox back into Vis, and the only one.

The embedded interpreter calls out through ONE function it was handed, so
everything a block can ask the host for arrives here as two strings: the name
of a tool and a JSON envelope of its arguments. What crosses is DATA - a tool
takes plain values and answers plain values, because the boundary carries
text and nothing else. A live object (an interpreter handle, a stream, a file)
crossed it and never will; a handle the model holds is a PYTHON object built
over calls that come back through here.

One interpreter serves every session in this process, so the envelope names
the session that called (`vis-python-runtime`'s `install`) and this registry
answers per session: `shell` bound for two workspaces is two different
functions under one name, and a session that never bound a name gets a
refusal that says so instead of a neighbour's tool.

The host is bound ONCE per process. Binding is idempotent because the
interpreter is: a second `bind!` would replace a live upcall stub while a
block sits inside it.
raw docstring

*guest-thread?*clj

True while the host serves a call from a thread that the guest created. Such a call has no active extension call, so its doors can limit what it does.

True while the host serves a call from a thread that the guest created.
Such a call has no active extension call, so its doors can limit what it does.
sourceraw docstring

bind!clj

(bind!)

Make dispatch the host THIS process's interpreter calls back into, if this process has an interpreter to bind.

Idempotent: the interpreter holds one upcall stub, and rebinding it while a block is inside a call would swap the target under a live frame. Answers whether a host is bound now.

Two things this used to get wrong, both measured while loading a Python extension in a process that never started a sandbox. It marked itself done BEFORE the bind, so a bind that threw left the flag set and every later caller skipped it — the first extension in such a process failed to load and the rest only appeared to work. And it insisted on the interpreter existing at all: binding needs the cdylib resolved, which a process that only talks to the extension host has no reason to have fetched. A library that will not resolve is not an error here, it is a process with nothing to bind — the extension host binds its OWN interpreter in its own process, and the parent binds when it builds a sandbox.

Make [[dispatch]] the host THIS process's interpreter calls back into, if this
process has an interpreter to bind.

Idempotent: the interpreter holds one upcall stub, and rebinding it while a
block is inside a call would swap the target under a live frame. Answers
whether a host is bound now.

Two things this used to get wrong, both measured while loading a Python
extension in a process that never started a sandbox. It marked itself done
BEFORE the bind, so a bind that threw left the flag set and every later caller
skipped it — the first extension in such a process failed to load and the rest
only appeared to work. And it insisted on the interpreter existing at all:
binding needs the cdylib resolved, which a process that only talks to the
extension host has no reason to have fetched. A library that will not resolve
is not an error here, it is a process with nothing to bind — the extension
host binds its OWN interpreter in its own process, and the parent binds when
it builds a sandbox.
sourceraw docstring

call-argsclj

(call-args args kwargs)

The argument vector a tool fn takes for Python args and kwargs.

Clojure tools consume options maps; Python callables recover the marked kwargs before invocation. User positional maps never carry this metadata.

The argument vector a tool fn takes for Python `args` and `kwargs`.

Clojure tools consume options maps; Python callables recover the marked
kwargs before invocation. User positional maps never carry this metadata.
sourceraw docstring

conveyingcljmacro

(conveying session & body)

Evaluate body, conveying the current dynamic bindings to session's host calls.

Evaluate `body`, conveying the current dynamic bindings to `session`'s host calls.
sourceraw docstring

conveying*clj

(conveying* session f)

Call f, conveying THIS thread's dynamic bindings to session's host calls.

A context has no invocation identity on its host callbacks, so its complete driver calls must run one at a time. Waiting is interruptible; other contexts remain independent. Reentrant calls restore the outer driver's frame.

Call `f`, conveying THIS thread's dynamic bindings to `session`'s host calls.

A context has no invocation identity on its host callbacks, so its complete
driver calls must run one at a time. Waiting is interruptible; other contexts
remain independent. Reentrant calls restore the outer driver's frame.
sourceraw docstring

dispatchclj

(dispatch caller tool payload)

Serve one call from the sandbox: WHO called, payload in, reply JSON out.

caller is the INTERPRETER's answer — the namespace the call was made from — and it is the only thing this authorizes against. The payload also carries a session, because the guest's envelope always did, but that field is written by the guest: a block that named a neighbour's session used to be served the neighbour's tools, with the neighbour's roots (measured: a confined block read a file its own policy had refused it one statement earlier). A mismatch is a forgery attempt and is recorded as one.

THE host function itself, kept public so a test can measure the boundary without an interpreter. It never throws: a failure is a reply the guest raises as RuntimeError, because a host that throws here unwinds through the interpreter's upcall stub.

Serve one call from the sandbox: WHO called, `payload` in, reply JSON out.

`caller` is the INTERPRETER's answer — the namespace the call was made from —
and it is the only thing this authorizes against. The payload also carries a
session, because the guest's envelope always did, but that field is written
by the guest: a block that named a neighbour's session used to be served the
neighbour's tools, with the neighbour's roots (measured: a confined block read
a file its own policy had refused it one statement earlier). A mismatch is a
forgery attempt and is recorded as one.

THE host function itself, kept public so a test can measure the boundary
without an interpreter. It never throws: a failure is a reply the guest
raises as `RuntimeError`, because a host that throws here unwinds through the
interpreter's upcall stub.
sourceraw docstring

forget-session!clj

(forget-session! session)

Drop session's bindings, answering how many names went.

The interpreter's namespace is closed by whoever owns the session; this is the host half, and leaving it behind would keep every closure a finished session captured.

Drop `session`'s bindings, answering how many names went.

The interpreter's namespace is closed by whoever owns the session; this is
the host half, and leaving it behind would keep every closure a finished
session captured.
sourceraw docstring

forget-tools!clj

(forget-tools! session names)

Drop names from session's registry, answering nothing.

For a tool that exists only for the length of ONE call — a host callback an adapter hands INTO Python — the registry is what makes the name callable, so forgetting it is what takes the capability back.

Drop `names` from `session`'s registry, answering nothing.

For a tool that exists only for the length of ONE call — a host callback an
adapter hands INTO Python — the registry is what makes the name callable, so
forgetting it is what takes the capability back.
sourceraw docstring

install-doors!clj

(install-doors! session tools)
(install-doors! session tools install-one)

Install tools into session AND register them for every session.

A shim door is ONE host capability, not a per-session closure, and Python is PROCESS state: attach and ls staple their entry points onto builtins, so a second session can find names the FIRST session built — holding proxies that still name it. The shared key is what keeps those doors answering after that session is gone.

Install `tools` into `session` AND register them for every session.

A shim door is ONE host capability, not a per-session closure, and Python is
PROCESS state: `attach` and `ls` staple their entry points onto `builtins`, so
a second session can find names the FIRST session built — holding proxies that
still name it. The shared key is what keeps those doors answering after that
session is gone.
sourceraw docstring

install-sync-tools!clj

(install-sync-tools! session tools)
(install-sync-tools! session tools install-one)

Bind tools as session's host tools that answer DIRECTLY, and answer the names bound.

For Python the host runs, not the model: a sandbox tool hands back a thunk the block runner settles, and trusted code - an extension calling vis.shell(...) in the middle of a line - has no runner and no await. The installer is a PARAMETER because trusted Python does not run where the sandbox does: an extension session binds its names in the unconfined child process, a sandbox session in this one, and the registry that answers them is the same either way.

Bind `tools` as `session`'s host tools that answer DIRECTLY, and answer the
names bound.

For Python the host runs, not the model: a sandbox tool hands back a thunk
the block runner settles, and trusted code - an extension calling
`vis.shell(...)` in the middle of a line - has no runner and no `await`.
The installer is a PARAMETER because trusted Python does not run where the
sandbox does: an extension session binds its names in the unconfined child
process, a sandbox session in this one, and the registry that answers them is
the same either way.
sourceraw docstring

install-tools!clj

(install-tools! session tools)
(install-tools! session tools bind-one)

Bind tools - {python-name fn} - as session's host tools and answer the names bound.

Registration and installation are ONE step on purpose: a name the guest can call but the registry does not know is a refusal the model cannot act on.

bind-one says WHERE the guest half is bound: this process's interpreter by default, or a session's own worker when the caller hands one in. The registry half is the parent's either way — a worker calls back here.

Bind `tools` - `{python-name fn}` - as `session`'s host tools and answer the
names bound.

Registration and installation are ONE step on purpose: a name the guest can
call but the registry does not know is a refusal the model cannot act on.

`bind-one` says WHERE the guest half is bound: this process's interpreter by
default, or a session's own worker when the caller hands one in. The registry
half is the parent's either way — a worker calls back here.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close