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
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.HTTPStatus
classset
req.response.status
directly.
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