Establishes the framework's foundation as a multi-site adoptable platform. ADRs (migrations/adr/): - ADR-001 (ACCEPTED): Asset is the platform contract; Machine retires. Three relationship types (partof, controls, connectedto) with free-text label, position-resolution chain (asset > related > location), hierarchical locations, sibling-bay propagation. - ADR-002 (ACCEPTED): Plugin contract semver via __contract_version__. - ADR-003 (ACCEPTED): Hybrid plugin distribution (in-tree bundled + filesystem-based external). - ADR-004 (ACCEPTED): Per-site instances, not multi-tenant. - ADR-005 (ACCEPTED): Equipment plugin (manufacturing) split from measuringtools plugin (metrology). Subtype-table pattern for protocol data (FOCAS, CLM, MTConnect). - ADR-006 (ACCEPTED): Plugin collector contract via get_collector_schema hook with API-key auth and identity-based upsert. Naming convention v1 (CONTRIBUTING.md): - DB tables/columns: lowercase concatenated, no underscores or dashes - DB-mirrored Python/JS variables match column names exactly; pure code follows host-language convention (PEP 8 / camelCase) - Closed acronym allowlist (universal + shop-floor domain), banned shorthand list with suffix exception (printers_bp etc allowed) - Plain ASCII everywhere: chat, docs, comments, string literals Style enforcement (scripts/check-naming-and-style.sh): - Pre-commit-runnable check script: non-ASCII, banned shorthand, snake_case DB names, snake_case API params in frontend - Fixes 14 violations across 11 files (Unicode arrows, snake_case params, ctx -> canvasContext, res -> response, req -> request_obj) Project state (CLAUDE.md, README.md, frontend/CLAUDE.md): - De-staled CLAUDE.md to reflect actual current state - README unifies DB story (MySQL canonical, SQLite test-only) - frontend/CLAUDE.md points at root convention Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.1 KiB
Python
44 lines
1.1 KiB
Python
"""Pagination utilities."""
|
|
|
|
from flask import request, current_app
|
|
from typing import Tuple
|
|
|
|
|
|
def get_pagination_params(request_obj=None) -> Tuple[int, int]:
|
|
"""
|
|
Extract pagination parameters from request.
|
|
|
|
Returns:
|
|
Tuple of (page, per_page)
|
|
"""
|
|
if request_obj is None:
|
|
request_obj = request
|
|
|
|
default_size = current_app.config.get('DEFAULT_PAGE_SIZE', 20)
|
|
max_size = current_app.config.get('MAX_PAGE_SIZE', 100)
|
|
|
|
try:
|
|
page = max(1, int(request_obj.args.get('page', 1)))
|
|
except (TypeError, ValueError):
|
|
page = 1
|
|
|
|
try:
|
|
per_page = int(request_obj.args.get('perpage', request_obj.args.get('per_page', default_size)))
|
|
per_page = max(1, min(per_page, max_size))
|
|
except (TypeError, ValueError):
|
|
per_page = default_size
|
|
|
|
return page, per_page
|
|
|
|
|
|
def paginate_query(query, page: int, per_page: int):
|
|
"""
|
|
Apply pagination to a SQLAlchemy query.
|
|
|
|
Returns:
|
|
Tuple of (items, total)
|
|
"""
|
|
total = query.count()
|
|
items = query.offset((page - 1) * per_page).limit(per_page).all()
|
|
return items, total
|