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()


@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

pymlconf

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:

These are some builtin HTTP status factory functions:

badrequest()

unauthorized()

forbidden()

notfound()

methodnotallowed()

conflict()

gone()

preconditionfailed()

notmodified()

internalservererror()

badgateway()

movedpermanently()

found()

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