Museum Loans
Museum object loan tracker API with overdue flags and urgency bands, explorable from the pxt CLI and dashboard
One app.py with 2 tables, 5 computed columns, 5 HTTP routes. Needs pixeltable[serve]>=0.7.14. Updated 2026-09-30.
app.py
"""Museum Loans API built with Pixeltable.pxt schema update app.py museumpxt service run app.py museum"""import pixeltable as pxtimport pixeltable.functions as pxtffrom pixeltable.serving import FastAPIRouterfrom udfs import is_overdue, loan_label, urgency# ---- tables ----TableModel = pxt.model_base()class Objects(TableModel, name='objects'):id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)accession: pxt.Stringtitle: pxt.Stringmedium: pxt.Stringyear: pxt.Intgallery: pxt.Stringaccession_upper = pxtf.string.upper(accession)title_lower = pxtf.string.lower(title)class Loans(TableModel, name='loans'):id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)accession: pxt.Stringborrower: pxt.Stringvenue: pxt.Stringstatus: pxt.Stringdays_out: pxt.Intnotes: pxt.String | Nonecurator: pxt.Stringlabel = loan_label(accession, borrower, venue)overdue = is_overdue(status, days_out)urgency_band = urgency(status, days_out)# ---- queries ----@pxt.querydef overdue_loans(curator: str):"""Overdue loans for one curator, most urgent first."""return Loans.where((Loans.curator == curator) & (Loans.overdue == True)).select( # noqa: E712Loans.id, Loans.label, Loans.days_out, Loans.urgency_band).order_by(Loans.days_out, asc=False)# ---- routes ----loans_api = FastAPIRouter(name='loans_api')loans_api.add_insert_route(Objects, path='/objects',inputs=[Objects.accession, Objects.title, Objects.medium, Objects.year, Objects.gallery],outputs=[Objects.id, Objects.accession_upper, Objects.title_lower])loans_api.add_insert_route(Loans, path='/loans',inputs=[Loans.accession, Loans.borrower, Loans.venue, Loans.status, Loans.days_out,Loans.notes, Loans.curator],outputs=[Loans.id, Loans.label, Loans.overdue, Loans.urgency_band])loans_api.add_update_route(Loans, path='/loans/update', inputs=[Loans.status, Loans.days_out],outputs=[Loans.id, Loans.overdue, Loans.urgency_band])loans_api.add_compute_route(Loans, path='/urgency', inputs=[Loans.status, Loans.days_out],outputs=[Loans.overdue, Loans.urgency_band])loans_api.add_query_route(path='/loans/overdue', query=overdue_loans, method='get')
What app.py declares
Each row links to its line.
- runs on insert
- stored
- index
- route
serves loans_api
- POST/objectsinsert, objectstakes accession, title, medium, year, gallery; returns id, accession_upper, title_lowerL54
- POST/loansinsert, loanstakes accession, borrower, venue, status, days_out, notes, curator; returns id, label, overdue, urgency_bandL57
- POST/loans/updateupdate, loanstakes status, days_out; returns id, overdue, urgency_bandL61
- POST/urgencycompute, loanstakes status, days_out; returns overdue, urgency_bandL63
- GET/loans/overduequery, overdue_loanstakes curatorL65
Run it
Needs uv. The routes then answer on port 8000.
terminal
git clone https://github.com/pierrebrunelle/pixeltable-museum-loanscd pixeltable-museum-loansuv syncuv run pxt schema update app.py museumuv run python seed.py museumuv run pxt service run app.py museum --port 8000
Related apps
- Forge Heat CyclesForge heat-cycle registry API: B-tree indexes, chained risk UDFs, and parallel shop-floor writersShares: UDFs
- Climbing Route ToposClimbing-route topo registry API: photo uploads with thumbnails, grayscale copies, grade bands and index-backed crag lookupsShares: UDFs
- Warehouse Pick ListsWarehouse pick-list REST API: units, pick zones, waves and tote labels as incremental computed columns over Json order linesShares: UDFs