Manual Routes¶
flarchitect can wrap your own Flask view functions with the same machinery it uses for generated endpoints. This is helpful when you hand‑craft a route but still want consistent authentication, schema validation/serialisation, rate limiting and OpenAPI documentation.
Use architect.schema_constructor to decorate a view and describe how it
should be treated. The decorator applies input/output Marshmallow schemas,
honours auth/roles config, attaches rate limiting, and registers the route for
documentation generation.
Reserving canonical paths¶
By default flarchitect registers RESTful collection/detail routes for every
model under the canonical prefix (for example /api/orders and
/api/orders/<id>). Projects that need bespoke logic for those URLs can turn
the automatic routes off while still relying on auto-generated schemas,
relations and alternate endpoints.
Disable the feature globally with
API_REGISTER_CANONICAL_ROUTES = False.Override per model via
Meta.register_canonical_routes = False.Supply alternate auto-generated endpoints with
Meta.endpoint(string or list) so CRUD handlers remain available under a different prefix.
Example:
class Config:
API_REGISTER_CANONICAL_ROUTES = False # keep /api/v1/orders for bespoke handlers
class Order(db.Model):
__tablename__ = "orders"
class Meta:
tag = "Order"
register_canonical_routes = False
endpoint = "orders/records" # auto CRUD lives here instead
@app.get("/api/v1/orders")
def bespoke_orders():
"""Your custom collection handler."""
...
The default remains True so existing projects keep their canonical routes
until they opt out. Relation endpoints and discovery continue to work because
the underlying services are still created.
Basic usage¶
from flask import Flask
from marshmallow import Schema, fields
from flarchitect import Architect
app = Flask(__name__)
app.config.update(
API_TITLE="My API",
API_VERSION="1.0",
# Enable JWT (or your chosen method) if you want auth enforced
API_AUTHENTICATE_METHOD=["jwt"],
)
architect = Architect(app)
class HelloOut(Schema):
message = fields.String(required=True)
@app.get("/hello")
@architect.schema_constructor(
output_schema=HelloOut, # serialise the return value with this schema
group_tag="Custom", # group in docs
auth=True, # enforce configured auth on this route
)
def hello():
return {"message": "world"}
The decorator automatically:
Validates/serialises using the provided schemas.
Enforces authentication (unless
auth=Falseis set).Applies any configured rate limit (see
API_RATE_LIMITand model/method overrides).Registers the route so it appears in the OpenAPI spec and docs.
Input and output schemas¶
You can validate request bodies and serialise responses with Marshmallow
schemas. Use input_schema for inbound data and output_schema for the
response. For endpoints returning a list, pass many=True to control how
serialisation is applied.
If you don’t want flarchitect to serialise the response, set
output_schema=None. In this mode the wrapper skips field selection and
Marshmallow dumping entirely and your handler’s return value (dict or list)
is wrapped unchanged in the standard JSON envelope.
class ItemIn(Schema):
name = fields.String(required=True)
class ItemOut(Schema):
id = fields.Integer(required=True)
name = fields.String(required=True)
@app.post("/items")
@architect.schema_constructor(input_schema=ItemIn, output_schema=ItemOut)
def create_item():
# Access validated input via Flask's request.json in your handler
...
return {"id": 1, "name": "example"}
Route handler signature¶
Decorated handlers may optionally accept deserialized_data to receive the
validated request body when input_schema is provided. Extra wrapper kwargs
such as model are filtered and only arguments declared in your function
signature are passed, so both of the following are valid:
@app.post("/echo")
@architect.schema_constructor(input_schema=ItemIn, output_schema=None)
def echo(deserialized_data=None):
return deserialized_data
# or
@app.post("/echo2")
@architect.schema_constructor(input_schema=ItemIn, output_schema=None)
def echo2(deserialized_data=None, **kwargs): # kwargs may include 'model'
return deserialized_data
Roles and authentication¶
If your application uses role‑based access control, supply roles to require
users to have specific roles on this route. By default, when authentication is
enabled globally, roles are enforced automatically for decorated routes.
@app.get("/admin/stats")
@architect.schema_constructor(output_schema=HelloOut, roles=["admin"]) # require the "admin" role
def admin_stats():
return {"message": "ok"}
To allow access when the user has any of multiple roles, either set
roles_any_of=True or pass a dict with {"roles": [...], "any_of": True}:
@app.get("/content/edit")
@architect.schema_constructor(output_schema=HelloOut, roles=["editor", "admin"], roles_any_of=True)
def edit_content():
return {"message": "ok"}
# equivalent
@app.get("/content/edit-alt")
@architect.schema_constructor(output_schema=HelloOut, roles={"roles": ["editor", "admin"], "any_of": True})
def edit_content_alt():
return {"message": "ok"}
To opt out of authentication for a specific manual route, set auth=False:
@app.get("/public/ping")
@architect.schema_constructor(output_schema=HelloOut, auth=False)
def public_ping():
return {"message": "pong"}
Alternatively, configure API_AUTH_REQUIREMENTS to opt HTTP verbs or route
flavours in/out of authentication at the config level. Manual routes decorated
with schema_constructor honour the map—use it to make every GET route
public, for instance, without adding auth=False repeatedly. Apply
API_ACCESS_POLICY when you need row-level checks so both generated and
manual endpoints share the same access logic.
When working with cookie-based credentials, combine API_AUTH_TOKEN_PROVIDERS
(for example ['cookie', 'header']) with the
load_user_from_cookie helper to populate current_user outside the
standard auth flow. Use flarchitect.utils.cookie_settings() to retrieve
project-aligned cookie keyword arguments (merged from API_COOKIE_DEFAULTS
and SESSION_COOKIE_*) whenever you issue or clear cookies.
Documentation metadata¶
schema_constructor records metadata so your manual routes show up in the
OpenAPI document and UI. Useful kwargs include:
group_tag: Group name used for sectioning in docs.summary: Short summary for the operation.tag: Additional tag label if needed.error_responses: Mapping of error codes to descriptions used in docs.
Additional helpers¶
If you only need to protect a manual route with JWT and don’t require schema
wrapping or documentation, you can use jwt_authentication directly:
from flarchitect.core.architect import jwt_authentication
@app.get("/profile")
@jwt_authentication
def profile():
return {"status": "ok"}
This decorator validates the Authorization: Bearer <token> header and sets
the current user context.