Request parametry s typovou kontrolou

... Petr Blahoš, 30. 3. 2017 Pyramid Python

Minule jsme si popsali, jak dostávat request parametry jako parametry funkcí. Jestliže se na věc díváme z pohledu potřeby dát kódu nějaký řád, narazíme brzo na problém. Stejně si nemůžeme být úplně jistí, kdo jak upraví request ve webovém prohlížeči, takže musíme dělat nějakou typovou kontrolu. Například můžeme chtít aby:

  • Parametr number byl vždycky číslo
  • Parametr name nebyl prázdný
  • Parametr position byl číslo nebo prázdný

Většina webových frameworků má nebo používá nějakou knihovnu na generování formulářů, a následně na jejich validaci. Proč je tedy nevyužít?

Colander

Pyramid používá colander a deform. Deform generuje html ze schémat definovaných v colanderu - to nás teď nezajímá. Colander je knihovna pro validaci, serializaci a deserializaci dat. To přesně potřebujeme. Jednoduše řečeno: V colanderu si nadefinujeme schéma, které říká: number má být číslo a nesmí být prázdné. Pak při zpracovávání request parametrů řekneme: Vezmi z request.params number a deserializuj ho pomocí schématu pro number.

Ke strojům

Vrátíme se k aplikaci, kterou jsme si udělali v minulém zápisku. Uděláme následující úpravy:

  • Do setup.py přidáme do requires colander
  • Budeme muset aplikaci znovu nainstalovat, pip install -e ".[testing]"
  • Přidáme nové view test4 takto:
    • Přidáme route do __init__.py
    • Ve views.py zkopírujeme view pro test2 a upravíme na test4

Nový dekorátor nazveme extractcheckedparams a používat se bude asi takto:

@view_config(route_name='test4', renderer='templates/test.mako')
@extractcheckedparams(
    text=colander.SchemaNode(colander.String(), missing=""),
    number=colander.SchemaNode(colander.Int(), missing=None),
)
def t4_view(context, request, text, number):
    print("text=%s, number=%s" % (text, number))
    return {'project': 'EXTPAR'}

A teď už konečně ten dekorátor.

from functools import wraps
import logging

import colander
from pyramid.view import view_config
from pyramid.httpexceptions import (HTTPBadRequest, )


LOGGER = logging.getLogger("extpar.views")


def extractcheckedparams(**field_defs):
    def e_decorator(func):
        @wraps(func)
        def func_wrapper(*args, **kwargs):
            if "request" in args[0].__dict__:
                params = args[0].request.params
            elif "params" in args[0].__dict__:
                params = args[0].params
            else:
                params = args[1].params
            for (k, v) in field_defs.items():
                try:
                    kwargs[k] = v.deserialize(params.get(k))
                except colander.Invalid:
                    LOGGER.error("Bad value: %s=%s", k, params.get(k))
                    raise HTTPBadRequest(
                        comment="Bad or missing parameter %s" % k
                    )
            return func(*args, **kwargs)
        return func_wrapper
    return e_decorator

A je to.

Závěr

Tento mechanizmus funguje pěkně v případě předpřipravených odkazů, jednoduchých i složitějších formulářů sloužících k dotazování, ale sám bych jej nepoužil např. pro editaci nebo ukládání databázových záznamů. K tomu totiž slouží jiné prostředky, konkrétně generování schémat a následně i formulářů z databázových modelů, ať už plně automatické, nebo s nějakou dopomocí. Tomu, a taky plnohodnotnému použití colander+deform se budeme věnovat někdy jindy.