Skip to content

blackbull.router

blackbull.router

Route registration and dispatch.

RouteGroup is re-exported lazily from app to avoid a circular import. Handler and middleware contracts are in docs/guide/routing.md.

QUERY = 'QUERY' module-attribute

The HTTP QUERY method (RFC 10008) — safe, idempotent, cacheable, with a request body.

http.HTTPMethod has no QUERY member (RFC 10008 postdates the 3.15 feature freeze; earliest stdlib arrival is 3.16), so BlackBull exports the method as a plain string usable anywhere a method is accepted::

from blackbull import QUERY

@app.route(path='/search', methods=[QUERY])
async def search(body: bytes): ...

Because http.HTTPMethod is a StrEnum, this string stays equal- and hash-compatible with a future HTTPMethod.QUERY member — routes registered with it need no migration.

ConfigurationError

Bases: Exception

Raised by Router.validate() when route definitions are inconsistent.

ErrorRouter

Resolve error handlers by exception MRO, status, then default.

__call__(key)

Decorator form: @errors(HTTPStatus.NOT_FOUND).

__setitem__(key, fn)

Register fn as the handler for a status or an exception class.

The status must be 4xx or 5xx: the annotation admits any HTTPStatus, but registering a handler for, say, 200 raises ValueError.

resolve(exc_class, status=None)

Use status after an exception MRO miss, before the default.

HTTPException

Bases: Exception

An exception carrying the HTTP status the dispatcher should report.

Raising this from a handler — or from the framework's own request-body adapter — makes BlackBull._dispatch answer with status instead of the generic 500, and (for 4xx) log it quietly as a client error rather than dumping a server traceback. detail is an optional human-readable message surfaced in development-mode error pages.

MethodNotApplicable

Bases: Exception

Raised when the path exists but the HTTP method is not allowed.

PathNotRegistered

Bases: KeyError

Raised when no registered path matches the requested path.

RouteInfo

Bases: NamedTuple

Immutable snapshot of a single registered route entry.

Returned by BlackBull.get_routes. One entry is produced per (route, method) pair — a route registered with methods=[GET, POST] yields two RouteInfo records.

Attributes:

Name Type Description
method str

HTTP method string (e.g. "GET", "BREW").

path str

URL template (e.g. "/api/echo/{name}").

name str

Endpoint name, or "" if the route was registered unnamed.

Router

String paths live in the routing trie (sole store, including {param} segments); raw re.Pattern routes live in self._raw_regex and are scanned only on a trie miss.

cache_max property writable

The lookup cache's entry bound; 0 disables caching. Writable, so the cache can be retuned after construction.

__contains__(item)

Accept either a plain str (path only) or a (path, method, scheme) tuple. True when the path was registered verbatim as a string route, or when any raw re.Pattern route matches it.

__getitem__(key)

key: (path: str, method: str | HTTPMethod, scheme: Scheme)

The query→miss→resolve→store flow; _resolve does the matching. A repeat of the same key skips resolution entirely after the first hit.

__setitem__(key, value)

Register value under (path, methods) or (path, methods, scheme), routing the path to whichever of the two stores the class docstring describes. An omitted scheme is stored as _ANY_SCHEME.

get_routes()

Return a snapshot of all registered routes as RouteInfo.

Routes are returned in registration order. A route registered with multiple methods (e.g. methods=[GET, POST]) produces one entry per method, in the order the methods were declared. The returned list is a fresh shallow copy — callers may sort, filter, or mutate it without affecting the live router.

register_converter(type_, converter)

Register converter to turn a handler that returns type_ into a sendable (a Response, str/bytes, or JSON-able).

route(methods=[HTTPMethod.GET], path='/', scheme=Scheme.http, functions=[], middlewares=[], name=None, accept_query=None)

Register a function or middleware chain in the routing table.

Three calling conventions: 1. Decorator with no extra middlewares (functions and middlewares both empty) — returns a decorator via route_fn. 2. functions=[...] — registers a pre-built chain immediately; returns None (same as before). 3. middlewares=[...] — returns a decorator; the decorated handler is appended to the middleware list before the chain is registered.

name registers the route for use with url_path_for(). accept_query is documented on BlackBull.route, which delegates here.

route_fn(methods=[HTTPMethod.GET], path='/', scheme=Scheme.http, name=None, accept_query=None)

Return a decorator that registers the decorated handler.

See BlackBull.route for accept_query.

url_path_for(name, /, **params)

Return the path for the named route with params substituted.

Raises KeyError if the name is unknown, ValueError if required params are missing.

validate()

Check all route definitions for consistency, then freeze the router.

Checks performed:

  • Every converter spec names a known converter.
  • Every path param appears in the handler signature (simplified handlers).
  • Converter output type matches the handler's annotation.

Raises ConfigurationError listing all violations found. Sets self._frozen = True on success so no further routes can be added. Called once at app boot from BlackBull.run or serve — handler bugs that violate the contract surface before the first request is served, not after.

UnprocessableQuery

Bases: HTTPException

RFC 10008 §2.2 — a well-formed QUERY whose contents cannot be processed.

Raise this from a QUERY handler when the request media type was accepted (so 400/415 do not apply) but the query itself is semantically invalid — references an unknown field, violates a constraint, etc. The dispatcher answers 422 Unprocessable Content and, being an HTTPException subclass, it flows through the normal error-router path and quiet 4xx logging.

request_media_type(conn)

Return the request's media type (Content-Type sans parameters), lowercased; '' when no Content-Type is present.