Cookbook¶
Query String¶
yhttp will dispatch query string to the python’s keywordonly argument
if defined in handler, see foo argument in the example below.
of-course, all query string will available as a dictionary via
req.query.
@app.route()
@text
def get(req, *, foo=None):
    bar = req.query.get('bar')
    return f'{foo if foo else "None"} {bar if bar else "None"}'
app.ready()
A painless way to test our code is bddrest.
from bddrest import Given, response, when, given
with Given(app, '/?foo=foo&bar=bar'):
    assert response.text == 'foo bar'
    when(query=given - 'foo')
    assert response.text == 'None bar'
Form¶
Use req.form as a dictionary to access the
submitted fields.
Changed in version 4.0: An easy way to get form values is:
req.query['field-name']
req.form['field-name']
req.files['field-name']
req.getform()['field-name']
req.getfiles()['field-name']
from yhttp.core import Application, text, statuses
app = Application('0.1.0', 'foo')
@app.route()
@text
def post(req):
     return req.getform()['foo']
app.ready()
from bddrest import Given, response, when, given, status
with Given(app, verb='POST', form={'foo': 'bar'}):
    assert status == 200
    assert response.text == 'bar'
    when(form=given - 'foo')
    assert status == 411
the form= parameter of the Given and when functions will send the
given dictionary as a urlencoded HTTP form, but you can also try
multipart content type.
from bddrest import Given, response, when, given, status
with Given(app, verb='POST', form={'foo': 'bar'}):
    assert status == 200
    assert response.text == 'bar'
with Given(app, verb='POST', multipart={'foo': 'bar'}):
    assert status == 200
    assert response.text == 'bar'
Settings¶
Use app.settings attribute to update global
settings instance for the application. this is an instance of
pymlconf.Root.
To update configuration just use the pymlconf.Mergable.merge() or
pymlconf.Root.loadfile() methods of the Application.settings
Just remember configration format is yaml.
app.settings.merge('''
db:
  url: postgres://user:pass@host/db
''')
app.settings.loadfile('path/to/conf.yml')
Then use your configration keys like:
url = app.settings.db.url
Note
Do not update the app.settings instance
after the Application.ready() is called.
See also
Debug Flag¶
You can do:
app.settings.debug = False
Or:
app.settings.merge('debug: false')  # YAML syntax
To prevent write stacktrace on error responses.
HTTP Status¶
There are tree ways to set HTTP status code for response:
- use - statuscode()decorator.
- raise an instance of - statuses.HTTPStatusclass
- set - req.response.statusdirectly.
These are some builtin HTTP status factory functions:
See the example below for usage:
from yhttp.core import statuses
@app.route()
def get(req):
    raise statuses.notfound()
app.ready()
This is how to use statuscode() decorator to specify response status
code for all requests.
from yhttp.core import statuscode
@app.route()
@statuscode('201 Created')
def get(req):
    return b'Hello'
app.ready()
HTTP Redirect¶
To redirect the request to another location raise a
statuses.movedpermanently() or statuses.found()
raise statuses.found('http://example.com')
Custom HTTP Status¶
Use statuses.status() to raise your very own status code and text.
raise statuses.status(700, 'Custom Status Text')
Or set req.response.status directly.
@app.route()
def get(req):
    req.response.status = '201 Created'
    return ...
Routing¶
the only way to register handler for http requests is
Application.route() decorator factory.
@app.route()                 # Default route
def get(req):
    ...
@app.route('/foo')           # Not match with: /foo/bar
def get(req):
    ...
@app.route('/books/(\d+)')   # Match with: /books/1
def get(req, id):
    ...
Handler function’s name will be used as HTTP verb. so, the get in the
example above stands for the HTTP GET method.
Path Parameters¶
All un-named and named capture groups (...), (?...) and
(?P<name>...) in the route expression  are unpacked as positional
arguments of the handler.
@app.route(r'/([a-z0-9]+)/bar/([a-z0-9]+)')
def get(req, arg1, arg2):
    ...
@app.route(r'/(\d+)/?(\w+)?')
def post(req, id, title=None):
    ...
You may use non-capturing version of reqular parentheses (?:...) to
specify to not capture and pass the group to the handler:
@app.route(r'/(\d+)(?:/(\w+))?')
def put(req, id, title=None):
    ...
Any Verb¶
Another approach is to us a single star * to catch any verb.
@app.route(verb='*')          # Match any HTTP verb
def any(req):
    ...
Added in version 3.1.
Static Contents¶
Application class has two methods: Application.staticfile()
and Application.staticdirectory() to complete this mission!
app.staticfile(r'/a\.txt', 'path/to/a.txt')
app.staticdirectory(r'/foo/', 'path/to/foo/directory')
app.staticdirectory(r'/foo/', 'path/to/foo/directory', default='index.txt')
Note
Do not use any regular expression group inside
Application.staticdirectory()’s pattern parameter.
Guard¶
yhttp has a very flexible request guard system. these are some
examples:
from yhttp.core import guard
@app.route()
@app.queryguard((
    guard.String('foo'),
), strict=True)
@app.bodyguard((
    guard.String('bar'),
    guard.String('baz', optional=True, default='123')
))
def post(req):
    pass
with Given(app, verb='post', query=dict(foo='foo'),
           form=dict(bar='bar', baz='baz')):
    assert status == 200
    when(form=given - 'bar')
    assert status == '400 bar: Required'
    when(form=given - 'baz', query=given + dict(baz='baz'))
    assert status == '400 Invalid field(s): baz'
    when(form=given - 'baz')
    assert status == 200