Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.veloiq.dev/llms.txt

Use this file to discover all available pages before exploring further.

The veloiq_framework package exposes every symbol you need to build a full-stack VeloIQ application: the app factory, SQLModel base classes, a relationship wrapper with cardinality metadata, automatic CRUD router generation, role-based and row-level access control decorators, and FastAPI dependencies for auth and database sessions.

Installation

pip install veloiq-framework

App factory

create_veloiq_app

from veloiq_framework import create_veloiq_app, VeloIQConfig

app = create_veloiq_app(config: VeloIQConfig | None = None, **kwargs) -> FastAPI
Creates and returns a fully configured FastAPI application. When config is omitted, VeloIQConfig is constructed from environment variables and any **kwargs you pass. The returned app is a standard FastAPI instance — you can add routes, middleware, or dependencies on top.
config
VeloIQConfig | None
A VeloIQConfig instance. If None, one is built from environment variables. See Configuration.
**kwargs
Any
Forwarded to VeloIQConfig when config is not provided. Accepts the same fields as VeloIQConfig.
from veloiq_framework import create_veloiq_app

app = create_veloiq_app()   # DATABASE_URL read from environment
create_veloiq_app raises ValueError if no DATABASE_URL can be determined from the config or the environment.

Base models

All base classes extend SQLModel. Declare your tables by inheriting from one of these classes with table=True.

FrameworkModel

Minimal base model providing a standard auto-increment integer primary key named id. Use this when you do not need timestamp columns.
from veloiq_framework import FrameworkModel

class Category(FrameworkModel, table=True):
    __tablename__ = "category"
    name: str
    slug: str
FieldTypeDescription
idOptional[int]Auto-increment primary key.

TimestampedModel

Extends FrameworkModel with automatic created_at and updated_at columns. The schema generator appends these two fields after all fields you declare, so they appear last in every list, form, and detail view.
from veloiq_framework import TimestampedModel

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    total: float
    status: str = "pending"
FieldTypeDescription
idOptional[int]Inherited from FrameworkModel. Auto-increment PK.
created_atOptional[datetime]Set on insert. Never overwritten by CRUD updates.
updated_atOptional[datetime]Updated automatically on every write.

StandardModel

CubicWeb-compatible base model for applications that require eid as the primary key and cw_ column naming conventions. The eid Python attribute maps to the cw_eid physical column. Use this only when migrating from CubicWeb or maintaining an existing CubicWeb schema; new applications should use FrameworkModel or TimestampedModel.
from veloiq_framework import StandardModel
from sqlmodel import Field
from sqlalchemy import Column, String

class LegacyItem(StandardModel, table=True):
    __tablename__ = "legacy_item"
    cw_name: str = Field(sa_column=Column("cw_name", String))
FieldTypeDescription
eidOptional[int]Maps to cw_eid column. Auto-increment PK.
creation_dateOptional[datetime]Set on insert.
modification_dateOptional[datetime]Updated automatically on every write.

Relationships

jm_relationship

jm_relationship(
    *,
    min_items: int = 0,
    max_items: int | None = None,
    required: bool = False,
    **kwargs: Any,
) -> Any
SQLModel Relationship wrapper that attaches cardinality metadata. The DynamicResource UI component reads this metadata to render required/optional indicators and pagination hints. All **kwargs are passed through to SQLModel’s Relationship.
min_items
int
default:"0"
Minimum number of related items (used for UI validation hints).
max_items
int | None
default:"None"
Maximum number of related items. None means unbounded.
required
bool
default:"False"
Whether the relation is required.
**kwargs
Any
Passed directly to SQLModel’s Relationship (e.g. back_populates, link_model).
from typing import List, Optional
from sqlmodel import Field
from veloiq_framework import TimestampedModel, jm_relationship

class Customer(TimestampedModel, table=True):
    __tablename__ = "customer"
    name: str
    orders: List["Order"] = jm_relationship(back_populates="customer")

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    customer_id: Optional[int] = Field(default=None, foreign_key="customer.id")
    customer: Optional["Customer"] = jm_relationship(back_populates="orders")

RelationCardinality

Dataclass stored in the SQLAlchemy relationship info dict by jm_relationship. You do not normally construct this directly.
FieldTypeDefaultDescription
min_itemsint0Minimum cardinality.
max_itemsint | NoneNoneMaximum cardinality (None = unbounded).
requiredboolFalseWhether at least one item is required.

get_pk_field_name

get_pk_field_name(model_cls: type) -> str
Returns the Python attribute name for the primary key of a mapped SQLModel class. Falls back to "id" if inspection fails.

CRUD router

create_crud_router

create_crud_router(
    model_class: Type[T],
    *,
    prefix: str | None = None,
    tags: list[str] | None = None,
    pk_type: type = int,
) -> APIRouter
Generates a FastAPI APIRouter with standard list, get, create, update, and delete endpoints for model_class. See CRUD Router for the full endpoint reference.
model_class
Type[T]
required
A SQLModel table class.
prefix
str | None
URL prefix for all routes. Defaults to /<tablename>.
tags
list[str] | None
OpenAPI tags. Defaults to [<tablename>].
pk_type
type
default:"int"
Python type of the primary key.
from veloiq_framework.crud import create_crud_router
from .models import Task

router = create_crud_router(Task)

Access control

VeloIQ provides three layers of declarative access control. All are opt-in and can be combined freely.

Method set constants

ConstantValueDescription
ALL_METHODS{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}All HTTP methods.
WRITE_METHODS{"GET", "POST", "PUT", "PATCH", "OPTIONS", "HEAD"}All methods except DELETE.
READ_METHODS{"GET", "OPTIONS", "HEAD"}Read-only methods.

RoleDef

@dataclass
class RoleDef:
    name: str
    methods: set[str]
    description: str = ""
    is_preset: bool = True
Defines a developer-declared role. Pass a list of RoleDef objects to VeloIQConfig.roles; they are upserted to the database on startup.

DEFAULT_ROLES

Built-in role definitions used when VeloIQConfig.roles is not overridden.
NameMethodsDescription
AdminALL_METHODSFull administrative access.
ManagerWRITE_METHODSCreate, edit, and view — no delete.
ViewerREAD_METHODSRead-only access.

model_access

@model_access(Manager=["list", "show"], Viewer=["list", "show"])
class Invoice(TimestampedModel, table=True):
    ...
Class decorator that restricts which Refine actions a role may perform on this specific model. Roles not mentioned inherit their global permissions unchanged. Exceptions are restrictive only — they can narrow access, never grant beyond a role’s global permissions.

veloiq_field

veloiq_field(
    *,
    read_roles: list[str] | None = None,
    write_roles: list[str] | None = None,
    **kwargs: Any,
) -> Any
SQLModel/Pydantic field with optional per-role read and write restrictions. Wraps pydantic.Field and stores role metadata in json_schema_extra so the schema generator emits readRoles/writeRoles into TypeScript schemas. The CRUD router enforces these restrictions at runtime.
read_roles
list[str] | None
Roles allowed to read this field. Absent means all roles can read it.
write_roles
list[str] | None
Roles allowed to write this field. Absent means all roles can write it.
**kwargs
Any
Passed through to pydantic.Field (e.g. default, description).
from veloiq_framework import veloiq_field, TimestampedModel

class Employee(TimestampedModel, table=True):
    __tablename__ = "employee"
    name: str
    department: str
    salary: float = veloiq_field(
        default=0.0,
        read_roles=["Admin"],
        write_roles=["Admin"],
    )
    notes: str = veloiq_field(
        default="",
        write_roles=["Admin", "Manager"],
    )

rebac

@rebac(
    *,
    filter=None,
    owner_field: str | None = None,
    tenant_field: str | None = None,
)
Class decorator for row-level access control. At least one of filter, owner_field, or tenant_field must be supplied. Multiple options are OR-combined — a row is visible if any rule allows it.
filter
callable | None
A lambda (user, cls, session) -> SQLAlchemy clause | True | False | None. Return a WHERE clause for permitted rows, True for no restriction, or False to deny all rows.
owner_field
str | None
Name of a column pointing to veloiq_user.id. Shorthand for a filter that matches cls.<field> == user["eid"].
tenant_field
str | None
Name of a column pointing to veloiq_tenant.id. Grants access when the tenant is one the authenticated user belongs to.
from veloiq_framework import rebac, TimestampedModel
from sqlmodel import Field

@rebac(owner_field="created_by")
class Note(TimestampedModel, table=True):
    created_by: int = Field(foreign_key="veloiq_user.id")

@rebac(tenant_field="tenant_id")
class Contract(TimestampedModel, table=True):
    tenant_id: int = Field(foreign_key="veloiq_tenant.id")
@rebac applies to all roles, including Admin. To exempt Admins, return True from the filter when the user has the Admin role. Inaccessible rows return 404, not 403, to avoid leaking which IDs exist.

rebac_subquery

rebac_subquery(model_class: type, user: dict, session) -> Subquery
Returns a SQLAlchemy subquery of primary keys of model_class rows that user may access. Designed to be called inside a @rebac(filter=…) lambda to express relationship-based access. The target model_class must itself carry a @rebac decorator. Raises ValueError on circular dependencies.
from veloiq_framework import rebac, rebac_subquery, TimestampedModel

@rebac(filter=lambda user, cls, session:
           cls.folder_id.in_(rebac_subquery(Folder, user, session)))
class Document(TimestampedModel, table=True):
    folder_id: int

Auth utilities

get_current_user

FastAPI dependency that returns the authenticated user payload decoded from the JWT Bearer token. The payload is a dict containing at minimum sub (user ID as string) and roles (list of role names).
from fastapi import Depends
from veloiq_framework import get_current_user

@router.get("/me")
def me(user=Depends(get_current_user)):
    return user

require_role

FastAPI dependency that raises HTTP 403 if the authenticated user does not hold at least one of the specified roles.
from fastapi import Depends
from veloiq_framework import require_role

@router.delete("/{id}")
def delete_item(id: int, _=Depends(require_role("Admin"))):
    ...

get_session

FastAPI dependency that yields a sqlmodel.Session bound to the configured database engine. Use it in custom endpoints that need direct database access.
from fastapi import Depends
from sqlmodel import Session, select
from veloiq_framework import get_session
from .models import Order

@router.get("/pending")
def list_pending(session: Session = Depends(get_session)):
    return session.exec(select(Order).where(Order.status == "pending")).all()

Configuration

Full VeloIQConfig field reference and environment variables.

CRUD Router

Auto-generated endpoint reference and query parameter details.