Primate

Python

Primate runs Python with WebAssembly compilation, strongly-typed validation, sessions, and server-side routing.

Setup

Install Python

First, install Python 3.8 or later from the official website.

Install Module

npm install @primate/python

Configure

import config from "primate/config";
import python from "@primate/python";

export default config({
  modules: [python()],
});

Initialize Python

Create a requirements.txt file in your project root to specify Python dependencies:

numpy

Install the Primate Python dependency:

pip install primate-run

Primate will automatically install packages listed in requirements.txt when compiling routes to WebAssembly.

Routes

Create Python route handlers in routes using .py files. Routes are compiled to WebAssembly and run in the JavaScript runtime.

# routes/hello.py
from primate import Route

@Route.get
def get(request):
    return "Hello, world!"

HTTP Methods

All standard HTTP methods are supported:

from primate import Route

@Route.get
def get(request):
    return "GET request"

@Route.post
def post(request):
    return "POST request"

@Route.put
def put(request):
    return "PUT request"

@Route.delete
def delete(request):
    return "DELETE request"

Request Handling

Query Parameters

Access query parameters through the query request bag:

# routes/query.py
from primate import Route

@Route.get
def get(request):
    if request.query.has("foo"):
        return request.query.get("foo")
    else:
        return "foo missing"

Request Body

Handle different body types based on content:

JSON Body

# routes/json.py
from primate import Route

@Route.post
def post(request):
    return request.body.json()

Form Fields

# routes/form.py
from primate import Route

@Route.post
def post(request):
    return request.body.fields()

Text Body

# routes/text.py
from primate import Route

@Route.post
def post(request):
    return request.body.text()

Binary Data

# routes/binary.py
from primate import Route

@Route.post
def post(request):
    bin_data = request.body.binary()

    return {
        "type": bin_data.content_type,
        "size": bin_data.size,
        "head": bin_data.head(4)
    }

File Uploads

Handle multipart file uploads:

# routes/upload.py
from primate import Route

@Route.post
def post(request):
    # get form fields
    fields = request.body.fields()

    # get uploaded files
    files = request.body.files()

    # process files
    file_info = []
    for file in files:
        file_info.append({
            "field": file.field,           # form field name
            "name": file.filename,         # original filename
            "type": file.content_type,     # MIME type
            "size": file.size,             # file size in bytes
            "content": file.io.read().decode("utf-8")  # file content
        })

    return {
        "fields": fields,
        "files": file_info
    }

Validation

Use Primate's strongly-typed validation system with the pema module:

# routes/validate.py
from primate import Route, pema

schema = pema.schema({
    "baz": pema.int(),
    "foo": pema.string()
})

@Route.get
def get(request):
    try:
        parsed = request.query.parse(schema, True)
        return parsed
    except pema.ValidationError as e:
        return str(e)

Field Types

The validation system supports multiple strongly-typed field types:

Coercion

Enable automatic type coercion by passing True as the second parameter:

parsed = request.query.parse(schema, True)  # enables coercion

With coercion enabled:

Responses

Plain Data

Return any Python object that can be JSON serialized:

@Route.get
def get(request):
    return {"name": "Donald"}

@Route.get
def get(request):
    return "Hello, world!"

@Route.get
def get(request):
    return [
        {"name": "Donald"},
        {"name": "Ryan"}
    ]

Views

Render components with props:

# routes/view.py
from primate import Route, Response

@Route.get
def get(request):
    return Response.view("index.html", {"hello": "world"})

With options:

from primate import Route, Response

@Route.get
def get(request):
    return Response.view("index.html",
        {"hello": "world"},
        {"partial": True}
    )

Redirects

Redirect to another route:

# routes/redirect.py
from primate import Route, Response

@Route.get
def get(request):
    return Response.redirect("/redirected")

With custom status code:

@Route.get
def get(request):
    return Response.redirect("/redirected", status=301)  # moved permanently

Error Responses

Return error responses:

# routes/error.py
from primate import Route, Response

@Route.get
def get(request):
    return Response.error()

With custom error options:

@Route.get
def get(request):
    return Response.error({"body": "Custom error message"})

Sessions

Manage user sessions with the Session module:

# routes/session.py
from primate import Route, Session

@Route.get
def get(request):
    # create a session
    Session.create({"foo": "bar"})

    # get session data
    return Session.get()

Session Methods

Python Conventions

Primate Python follows standard Python conventions:

Function Names

Dictionary Access

Use standard Python dictionary syntax:

# accessing form data
fields = request.body.fields()
name = fields.get("name", "")

# building responses
return {
    "status": "success",
    "data": processed_data
}

Exception Handling

Use try-except blocks for error handling:

@Route.post
def post(request):
    try:
        data = request.body.json()
        return process_data(data)
    except Exception as e:
        return {"error": str(e)}

External Packages

Install Python packages by adding them to requirements.txt:

numpy
pandas

Use them in your routes:

# routes/data.py
from primate import Route
import numpy as np
import pandas as pd

@Route.get
def get(request):
    # Create sample data
    data = np.array([1, 2, 3, 4, 5])
    df = pd.DataFrame({"values": data})

    return {
        "mean": float(np.mean(data)),
        "sum": int(np.sum(data)),
        "dataframe_info": df.describe().to_dict()
    }

Configuration

Option Type Default Description
fileExtension string ".py" Associated file extension

Example

import python from "@primate/python";
import config from "primate/config";

export default config({
  modules: [
    python({
      // use `.python` as associated file extension
      fileExtension: ".python",
    }),
  ],
});

Resources

Previous
Grain
Next
Ruby