Skip to main content

The opinionated Indie Maker Python web framework for shipping MVPs stupid-fast.

Project description

Shipy

The opinionated Indie Maker Python web framework for shipping MVPs stupid-fast.

  • App, Request, Response, routing (imperative)
  • Jinja2 templates via shipy.render.render()
  • HTMX support for interactive UI without complex JavaScript
  • SQLite helpers in shipy.sql (query, one, execute, tx)
  • Built-in auth with users/sessions + @login_required decorator
  • Request middleware for per-request data and short-circuiting
  • Signed-cookie sessions with CSRF + flash in shipy.session
  • CLI: shipy new, shipy dev, shipy db init, shipy deploy

Note: The SQL write helper is named execute (not exec, since exec is a Python keyword).

Quick start

pip install shipy-web
shipy new myapp && cd myapp
shipy db init
shipy dev

Visit http://localhost:8000 and sign up to get started!

Routing

Shipy supports all standard HTTP methods with imperative routing:

from shipy.app import App

app = App()

# Standard HTTP methods
app.get("/", home)
app.post("/users", create_user)
app.put("/users/{id}", update_user)
app.patch("/users/{id}", partial_update_user)
app.delete("/users/{id}", delete_user)

# Path parameters are available in req.path_params
def update_user(req):
    user_id = req.path_params.get("id")
    # ... update logic ...
    return Response.text(f"Updated user {user_id}")

Coding Standards

Code Organization

Use section comments to organize your code for better readability:

Python files (app/main.py):

# ---- Imports ----
from shipy.app import App, Response

# ---- App Setup ----
app = App()

# ---- Utilities ----
def helper_function():
    pass

# ---- Middleware ----
@app.middleware("request")
def my_middleware(req):
    pass

# ---- Route Handlers ----
def home(req):
    pass

# ---- Routes ----
app.get("/", home)

HTML templates:

<!DOCTYPE html>
<html>
  <head>
    <!-- ---- Meta & Head ---- -->
    <meta charset="utf-8" />
    <title>Page Title</title>
  </head>
  <body>
    <!-- ---- Header ---- -->
    <header>...</header>

    <!-- ---- Main Content ---- -->
    <main>...</main>

    <!-- ---- Footer ---- -->
    <footer>...</footer>
  </body>
</html>

This consistent organization makes code easier to navigate and maintain.

Example

See examples/hello/app/main.py:1 and examples/hello/app/views/home/index.html:1.

CLI

  • shipy new <name> — creates an auth-first scaffolded project in ./<name>
  • shipy dev [--app app.main:app] [--host 127.0.0.1] [--port 8000] — run development server
  • shipy version — show current version
  • shipy gensecret — generate a random SHIPY_SECRET for production
  • shipy db init [--db ./data/app.db] [--schema data/schema.sql] — initialize database
  • shipy db backup [--db ./data/app.db] [--out data/backups] — create database backup
  • shipy db run <path.sql> [--db ./data/app.db] — execute SQL script
  • shipy db make-migration <name> [--dir data/migrations] — create timestamped migration file
  • shipy db ls [--dir data/migrations] — list migration files
  • shipy db shell [--db ./data/app.db] — open interactive sqlite3 shell
  • shipy deploy emit [--path deploy] — generate systemd + nginx configs

Templates

Default template root is app/views. Use shipy.render.render('path/to/template.html', ctx, request=req).

HTMX Support

Shipy includes built-in HTMX support for interactive UI without complex JavaScript:

from shipy.render import render_htmx, is_htmx_request

# Use render_htmx for HTMX-enhanced templates
def home(req):
    todos = query("SELECT * FROM todos ORDER BY created_at DESC")
    return render_htmx(req, "home/index.html", todos=todos)

# Check if request is from HTMX
def todo_create(req):
    if is_htmx_request(req):
        # Return partial for HTMX
        return render_htmx(req, "todos/list.html", todos=todos)
    else:
        # Return full page
        return render_req(req, "home/index.html", todos=todos)

HTMX templates have access to htmx context:

  • htmx.request - true if request is from HTMX
  • htmx.target - HTMX target element
  • htmx.trigger - HTMX trigger event
<!-- HTMX form with partial updates -->
<form hx-post="/todos" hx-target="#todo-list" hx-swap="innerHTML">
  <input name="title" placeholder="New todo" />
  <button>Add</button>
</form>

<div id="todo-list">
  {% for todo in todos %}
  <div hx-delete="/todos/{{ todo.id }}" hx-target="this" hx-swap="outerHTML">
    {{ todo.title }}
    <button>Delete</button>
  </div>
  {% endfor %}
</div>

HTMX is included by default in scaffolded apps via CDN.

HTMX Response Helpers

For advanced HTMX interactions, use these response helpers:

from shipy.app import Response

# HTMX redirect to specific target
def update_profile(req):
    # ... update logic ...
    return Response.htmx_redirect("/profile", target="#main")

# HTMX refresh (reload the page)
def reset_form(req):
    return Response.htmx_refresh()

Middleware

Request middleware runs on every request after the Request is constructed but before route handlers. Use @app.middleware("request") to register middleware functions.

# Attach commonly used data to req.state
@app.middleware("request")
def attach_user(req):
    req.state.user = current_user(req)
    req.state.csrf_token = get_csrf_token(req)

# Short-circuit with a Response (e.g., maintenance mode)
@app.middleware("request")
def maintenance_mode(req):
    if maintenance_enabled:
        return Response.text("Site under maintenance", 503)

# Use in route handlers
def home(req):
    user = req.state.user  # Available via middleware
    return render_req(req, "home.html", user=user)

Middleware can:

  • Attach data to req.state for easy access in handlers
  • Short-circuit requests by returning a Response
  • Handle errors and logging
  • Add per-request objects (DB connections, etc.)

Sessions

Session data is stored in a signed cookie using itsdangerous. Set SHIPY_SECRET in your environment for production.

Flash messages are available via req.session.flash(message, category) and req.session.get_flashed_messages() in requests. CSRF token via req.session.get_csrf_token().

Authentication

Shipy includes built-in authentication helpers:

from shipy.auth import current_user, login_required, login, logout

# Check if user is logged in
user = current_user(req)

# Protect routes with @login_required decorator
@login_required()
def secret(req):
    # req.state.user is guaranteed to exist here
    return render_req(req, "secret.html", user=req.state.user)

# Custom redirect for unauthenticated users
@login_required(redirect_to="/custom-login")
def admin(req):
    return render_req(req, "admin.html", user=req.state.user)

# Manual authentication (for custom logic)
def manual_auth(req):
    user = current_user(req)
    if not user:
        return Response.redirect("/login")
    return render_req(req, "page.html", user=user)

The @login_required decorator:

  • Redirects unauthenticated users to /login (or custom path)
  • Automatically attaches user to req.state.user
  • Works with middleware for additional per-request data

Database

SQLite helpers in shipy/sql.py:1 use ./data/app.db by default (created as needed).

Override the database path with SHIPY_DB environment variable:

SHIPY_DB=/path/to/custom.db shipy dev
  • query(sql, params=()) -> list[dict]
  • one(sql, params=()) -> dict | None
  • execute(sql, params=()) -> int
  • with tx() as conn: conn.execute(...)

Apply schema with shipy db init --schema data/schema.sql.

Migrations

For one-off database changes, use the simple migration system:

# Create a migration file
shipy db make-migration "add user profiles"

# Edit the generated file in data/migrations/
# Then apply it
shipy db run data/migrations/20250926185037_add_user_profiles.sql

# List all migrations
shipy db ls

# Open interactive sqlite3 shell for debugging
shipy db shell

Migrations are plain SQL files wrapped in transactions for atomicity. No complex migration engine - just timestamped files you can read and understand.

License

MIT.

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

shipy_web-0.3.3.tar.gz (20.4 kB view details)

Uploaded Source

Built Distribution

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

shipy_web-0.3.3-py3-none-any.whl (23.9 kB view details)

Uploaded Python 3

File details

Details for the file shipy_web-0.3.3.tar.gz.

File metadata

  • Download URL: shipy_web-0.3.3.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for shipy_web-0.3.3.tar.gz
Algorithm Hash digest
SHA256 a4ec0dfc2c12bf76fcce6ad81c5b64b0aabec2ee280175d98c1d9e1a13fb211e
MD5 550cc2b11ffdd25e3eb6f317ae791fd7
BLAKE2b-256 0266e93e1ceedbda9edfeee6c173efa58c987fc90e35bac00729125a63da99ff

See more details on using hashes here.

File details

Details for the file shipy_web-0.3.3-py3-none-any.whl.

File metadata

  • Download URL: shipy_web-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 23.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for shipy_web-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 469e493512aeba710570a9693173bba01283df4517b1963f9148f2b14ad917d1
MD5 fa09bcda82a13f88057d59d9591a6492
BLAKE2b-256 ecb79a0161a345e84a2876d456af0cda93dcd2a60402d4b45cf5f59614ff6140

See more details on using hashes here.

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