Ferry Booking
Ferry booking REST API: fares and deck classes as incremental computed columns, served with FastAPI
One app.py with 2 tables, 3 computed columns, 9 HTTP routes. Needs pixeltable[serve]>=0.7.14. Updated 2026-10-07.
app.py
"""Ferry Booking API built with Pixeltable.Two tables (sailings, bookings) with the fare, deck class and booking id as computed columns,served as a REST API by a single FastAPIRouter: insert, update, delete, compute (quote) andquery routes (POST, GET, one-row lookups).Layout: UDFs live in udfs.py (imported below); this file holds the tables, the @pxt.query functions and the routes.Run locally:pxt schema update app.py ferrypxt service run app.py ferrySee README.md for the walkthrough and Pixeltable Cloud deployment."""import pixeltable as pxtimport pixeltable.functions as pxtffrom pixeltable.serving import FastAPIRouterfrom udfs import deck, extras_keys, fare_cents# ---- tables ----TableModel = pxt.model_base()class Sailings(TableModel, name='sailings', has_default_idxs=False):sailing_id = pxt.Column(type=pxt.String, primary_key=True)route: pxt.Stringdepart_at: pxt.Stringvessel: pxt.Stringpax_capacity: pxt.Int__indexes__ = [pxt.BtreeIndex(route)]class Bookings(TableModel, name='bookings', has_default_idxs=False):booking_id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)sailing_id: pxt.Stringlead_name: pxt.Stringparty_size: pxt.Intvehicle_len_m: pxt.Float | Noneextras: pxt.Json | Nonestatus: pxt.String# computed columns: evaluated on insert/update, recomputed when inputs changefare = fare_cents(party_size, vehicle_len_m, extras)deck_class = deck(vehicle_len_m)extras_order = extras_keys(extras)__indexes__ = [pxt.BtreeIndex(sailing_id), pxt.BtreeIndex(status)]# ---- queries ----@pxt.querydef manifest(sailing_id: str):"""All bookings on a sailing (index-backed)."""return (Bookings.where(Bookings.sailing_id == sailing_id).select(Bookings.booking_id, Bookings.lead_name, Bookings.party_size, Bookings.deck_class,Bookings.fare, Bookings.status).order_by(Bookings.lead_name))@pxt.querydef booking_by_lead(lead_name: str):"""One-row lookup: 0 rows -> 404, more than one -> 409."""return Bookings.where(Bookings.lead_name == lead_name).select(Bookings.booking_id, Bookings.sailing_id, Bookings.fare, Bookings.status)@pxt.querydef sailings_on_route(route: str):"""Sailings on a route, in departure order."""return Sailings.where(Sailings.route == route).select(Sailings.sailing_id, Sailings.depart_at, Sailings.vessel, Sailings.pax_capacity).order_by(Sailings.depart_at)# ---- routes ----booking_api = FastAPIRouter(name='booking_api')booking_api.add_insert_route(Bookings,path='/bookings',inputs=[Bookings.sailing_id, Bookings.lead_name, Bookings.party_size, Bookings.vehicle_len_m,Bookings.extras, Bookings.status],outputs=[Bookings.booking_id, Bookings.fare, Bookings.deck_class, Bookings.extras, Bookings.extras_order],)booking_api.add_update_route(Bookings,path='/bookings/update',inputs=[Bookings.status, Bookings.party_size],outputs=[Bookings.booking_id, Bookings.status, Bookings.party_size, Bookings.fare],)booking_api.add_delete_route(Bookings, path='/bookings/cancel')booking_api.add_compute_route(Bookings,path='/quote',inputs=[Bookings.party_size, Bookings.vehicle_len_m, Bookings.extras],outputs=[Bookings.fare, Bookings.deck_class],)booking_api.add_query_route(path='/manifest', query=manifest)booking_api.add_query_route(path='/manifest-get', query=manifest, method='get')booking_api.add_query_route(path='/booking/by-lead', query=booking_by_lead, one_row=True, method='get')booking_api.add_query_route(path='/sailings', query=sailings_on_route, method='get')booking_api.add_compute_route(Bookings, path='/quote-v2', inputs=[Bookings.party_size, Bookings.vehicle_len_m],outputs=[Bookings.fare])
What app.py declares
Each row links to its line.
- runs on insert
- stored
- index
- route
serves booking_api
- POST/bookingsinsert, bookingstakes sailing_id, lead_name, party_size, vehicle_len_m, extras, status; returns booking_id, fare, deck_class, extras, extras_orderL83
- POST/bookings/updateupdate, bookingstakes status, party_size; returns booking_id, status, party_size, fareL90
- POST/bookings/canceldelete, bookingsL96
- POST/quotecompute, bookingstakes party_size, vehicle_len_m, extras; returns fare, deck_classL97
- POST/manifestquery, manifesttakes sailing_idL103
- GET/manifest-getquery, manifesttakes sailing_idL104
- GET/booking/by-leadquery, booking_by_leadtakes lead_nameL105
- GET/sailingsquery, sailings_on_routetakes routeL106
- POST/quote-v2compute, bookingstakes party_size, vehicle_len_m; returns fareL107
Run it
Needs uv. The routes then answer on port 8000.
terminal
git clone https://github.com/pierrebrunelle/pixeltable-ferry-bookingscd pixeltable-ferry-bookingsuv syncuv run pxt schema update app.py ferryuv run python seed.py ferryuv run pxt service run app.py ferry --port 8000
Related apps
- Forge Heat CyclesForge heat-cycle registry API: B-tree indexes, chained risk UDFs, and parallel shop-floor writersShares: B-tree indexes, UDFs
- Climbing Route ToposClimbing-route topo registry API: photo uploads with thumbnails, grayscale copies, grade bands and index-backed crag lookupsShares: B-tree indexes, UDFs
- Warehouse Pick ListsWarehouse pick-list REST API: units, pick zones, waves and tote labels as incremental computed columns over Json order linesShares: B-tree indexes, UDFs