Skip to main content

Signed snippets, hiccup, and a dual-eval loop. The web that should have been.

Project description

evaleval

evaleval is a tiny server-driven web pattern: signed Python expressions in forms, eval on submit, and UI patching over SSE.

The entire client.

import { Idiomorph } from 'idiomorph';
window.Idiomorph = Idiomorph;
const es = new EventSource('/sse');
es.addEventListener('exec', e => eval(e.data));
document.addEventListener('submit', async e => {
  e.preventDefault();
  const r = await fetch(e.target.action, { method: 'POST', body: new FormData(e.target) });
  const t = await r.text();
  if (t) eval(t);
});

Three endpoints. No framework.

GET  /       — serve the shell
GET  /sse    — push what you see
POST /     — verify, eval

Example: evaleval-todo

A todo list in ~170 lines.

Forms are data. They carry their own signed handlers.

from evaleval import Signer, Three, Two, Selector, MORPH, APPEND, REMOVE

signer = Signer()

def add_form():
    return ["form", {"action": "/", "method": "post"},
        *signer.snippet_hidden("add($text)"),
        ["input", {"type": "text", "name": "text", "placeholder": "what needs doing?"}],
        ["button", {"type": "submit"}, "add"],
    ]

Sandbox functions return JS patch chains. The chains say how many parts they have, then become strings and disappear.

def add(text):
    t = {"id": uuid.uuid4().hex[:8], "text": text, "done": False}
    TODOS.append(t)
    return PlainTextResponse(";".join([
        Three[Selector("#add-form")][MORPH][add_form()],
        Three[Selector("#todo-list")][APPEND][todo_item(t)],
        Three[Selector("p.count")][MORPH][remaining_count()],
    ]))

def delete(todo_id):
    TODOS.remove(_find(todo_id))
    return PlainTextResponse(";".join([
        Two[Selector(f"#todo-{todo_id}")][REMOVE],
        Three[Selector("p.count")][MORPH][remaining_count()],
    ]))

The POST / route verifies the signature and evals the snippet. verify_snippet raises SnippetExecutionError with a status code.

from evaleval import SnippetExecutionError

@app.post("/")
async def do(request):
    form = await request.form()
    try:
        snippet = signer.verify_snippet(form)
        return eval(snippet)
    except SnippetExecutionError as e:
        return PlainTextResponse(e.message, status_code=e.status_code)
    except Exception as e:
        return PlainTextResponse(str(e), status_code=500)

eval only accepts a single expression. Multi-line snippets and return ... statements will fail with SyntaxError.

If you want side effects plus a return value, use an expression pattern like:

(print("form callback for add todo", $text), add($text))[1]

SSE pushes the initial page as JS the browser evals.

from evaleval import exec_event, shell_html, One, Eval

@app.get("/")
async def index():
    return HTMLResponse(shell_html())

@app.get("/sse")
async def sse(request):
    async def generate():
        yield exec_event([
            One[Eval("document.title = 'todos'")],
            Three[Selector("body")][MORPH][["body", page()]],
        ])
    return StreamingResponse(generate(), media_type="text/event-stream")

pip install evaleval

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

evaleval-0.2.3.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

evaleval-0.2.3-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file evaleval-0.2.3.tar.gz.

File metadata

  • Download URL: evaleval-0.2.3.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for evaleval-0.2.3.tar.gz
Algorithm Hash digest
SHA256 386c898a2712c154dc708b8f49e3ae780e22026e97b8ea99b95959cab2713c91
MD5 357e7be53c4f4aa94f24166798186f15
BLAKE2b-256 e3673fa66d3963b9d1aecd87d3aff45944dc485ddbc13c505accdd983d1c7dac

See more details on using hashes here.

Provenance

The following attestation bundles were made for evaleval-0.2.3.tar.gz:

Publisher: publish.yml on tommy-mor/evaleval

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file evaleval-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: evaleval-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 8.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for evaleval-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 3f26d935af9dd7d472dd93f32a28458c9929c5762574e4502a0bc5357c2da61e
MD5 b949a25ef2553fd9cc9bd4a937ec0e5c
BLAKE2b-256 88e5afca13fed9352da17f2fb25050e58f0ecaa2d12b25ad0c91732e5406a12b

See more details on using hashes here.

Provenance

The following attestation bundles were made for evaleval-0.2.3-py3-none-any.whl:

Publisher: publish.yml on tommy-mor/evaleval

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page