GitHub

Integration & Extensibility (ASGI & Python hooks)

So far we have run PageQL from the command line. In real projects you often need to embed it in a larger Python stack—add authentication, custom SQL functions, or inject data before a template executes. This chapter shows how.

The code below relies only on uvicorn, pageql, and the Python std‑lib.

« Part 6: Enhancing Interactivity with HTMX

1 A minimal programmable server


import pageql, argparse, uvicorn, base64

parser = argparse.ArgumentParser(description="PageQL programmable server")
parser.add_argument('--db',   default='data.db')
parser.add_argument('--dir',  default='templates')
parser.add_argument('--port', type=int, default=8000)
parser.add_argument('--create', action='store_true')
parser.add_argument('--no-reload', action='store_true')
args = parser.parse_args()

app = pageql.PageQLApp(
    args.db,
    args.dir,
    create_db=args.create,
    should_reload=not args.no_reload,
)

# 1️⃣  Add a custom SQLite function
app.conn.create_function('base64_encode', 1,
    lambda blob: base64.b64encode(blob).decode())

# 2️⃣  Inject data before rendering any template that matches /before
@app.before('/before')
async def inject(params):
    params['title'] = 'Horse Power 🐴'
    with open('horse.jpg', 'rb') as f:
        params['image'] = f.read()  # raw bytes
    return params  # merged into template namespace

print(f"Visit http://localhost:{args.port}/before to test the hook")
uvicorn.run(app, host='0.0.0.0', port=args.port)
import pageql, argparse, uvicorn, base64 parser = argparse.ArgumentParser(description="PageQL programmable server") parser.add_argument('--db', default='data.db') parser.add_argument('--dir', default='templates') parser.add_argument('--port', type=int, default=8000) parser.add_argument('--create', action='store_true') parser.add_argument('--no-reload', action='store_true') args = parser.parse_args() app = pageql.PageQLApp( args.db, args.dir, create_db=args.create, should_reload=not args.no_reload, ) # 1️⃣ Add a custom SQLite function app.conn.create_function('base64_encode', 1, lambda blob: base64.b64encode(blob).decode()) # 2️⃣ Inject data before rendering any template that matches /before @app.before('/before') async def inject(params): params['title'] = 'Horse Power 🐴' with open('horse.jpg', 'rb') as f: params['image'] = f.read() # raw bytes return params # merged into template namespace print(f"Visit http://localhost:{args.port}/before to test the hook") uvicorn.run(app, host='0.0.0.0', port=args.port)

How it works

LineWhat happens
PageQLApp(...)Embeds the template engine in an ASGI app compatible with any framework/server.
create_functionExposes pure‑Python helpers to every SQL statement in your templates.
@app.before('/before')Registers an async function that runs before the template. You can also use @app.after() for post‑processing.

2 Tiny template that consumes the hook

Create templates/before.pageql:


<h1>{{title}}</h1>
<img src="data:image/jpeg;base64,{{{ base64_encode(image) }}}" width="320">
<p>This page was populated entirely from the <code>@before</code> hook.</p>
<h1>{{title}}</h1> <img src="data:image/jpeg;base64,{{{ base64_encode(image) }}}" width="320"> <p>This page was populated entirely from the <code>@before</code> hook.</p>

Reload the browser and you should see your local horse.jpg embedded via Data‑URI. Switch images at runtime or add extra parameters—no template change required.

Tip: If you don't have a horse.jpg, drop any JPEG into the project root or modify the hook to fetch from the internet.

3 Recap

Because PageQLApp is just ASGI, you can:

That concludes the tutorial series—happy hacking! 🎉

« Back: Part 6: Enhancing with HTMX