Overview of a Fresco application¶
Here’s a minimal application:
from fresco import FrescoApp, GET, Response
def helloworld():
return Response(["<h1>Hello World!</h1>"])
app = FrescoApp()
app.route('/', GET, helloworld)
Apps¶
At the core of a Fresco application is a FrescoApp
object:
from fresco import FrescoApp
app = FrescoApp()
The app
object is the entry point to your web application:
it receives requests (via the WSGI protocol)
and routes them to whatever view functions you have configured.
You can add view functions to your app by calling
app.route
,
specifying the URL and HTTP methods the view should respond to:
from fresco import GET, POST
app.route('/', GET, homepage)
app.route('/feedback-form', [GET, POST], feedback_form)
Views¶
A view is a function that handles an individual web request and returns a
Response
. For example:
def homepage():
return Response(["Hello World!"])
Views can also raise certain exceptions,
for example NotFound
or Forbidden
.
Views can also take arguments that are specified by the url routing. For
example this view requires a page
argument:
from fresco import Response
from fresco.exceptions import NotFound
def showpage(page):
page = get_page_from_database(page)
if page is None:
raise NotFound()
return Response([page.content_html])
The value that is passed to your view depends on your routing configuration.
Routes¶
Routes map between URLs and view functions, based on the URL and HTTP methods. In addition, routes can extract values from the URL:
# GET /content/about-us → calls showpage(page='about-us')
app.route('/content/<page:str>', GET, showpage)
Or from the query string:
# GET /content?page=faq → calls showpage(page='faq')
app.route('/content', GET, showpage, page=QueryArg())
View arguments can also be provided as fixed valuess:
# GET / → calls showpage(page='index')
app.route('/', GET, showpage, page='index')
Routing using function decorators¶
For convenience, app.route
acts as a function decorator, so you can define
your routes and view functions at the same time:
@app.route('/', GET)
def myview():
return Response(['hello world'])
While this can be useful for small projects, for large apps you may find placing route definitions together in a single block easier to manage.
View classes¶
Views that logically belong together can be grouped together in classes, for example:
from fresco import Route, GET, POST, Response
class ContactFormViews(object):
__routes__ = [
Route('/', GET, 'form'),
Route('/', POST, 'handle_submit'),
Route('/thanks', GET, 'thank_you'),
]
def __init__(self, recipient):
self.recipient = recipient
def form(self, errors=None):
return render('templates/contact-form.html', {'errors': errors})
def thank_you(self, errors=None):
return render('templates/contact-thanks.html', {'errors': errors})
def handle_submit(self):
try:
send_mail(toaddr=self.recipient,
fromaddr=context.request.get('fromaddr'),
message=context.request.get('message'))
except Exception as e:
return self.form(context.request,
errors=["Your message could not be sent"])
return Response.redirect(urlfor(self.thank_you))
Any method can be exposed as a view by adding its name to the __routes__
attribute. To include your class in an application, use
include()
:
app = FrescoApp()
app.include('/contact', ContactFormViews(recipient=['bob@example.com']))
As well as providing logical groupings of related views, class based views provide a modular way to reuse views in different parts of your application:
sales_enquiry_views = ContactFormViews(recipient=['sales@example.com'])
support_request_form = ContactFormViews(recipient=['support@example.com'])
app.include('/sales/enquiry', sales_enquiry_views)
app.include('/support/report-problem', support_request_form)
View modules¶
Any object that has a __routes__
attribute can be include in a fresco
application.
This means that as well as view classes, you can have view
modules, by including a __routes__
attribute in your module definition. For example:
# mypackage/routes.py
from fresco import Route, GET
from mypackage import views
__routes__ = [
Route('/', GET, views.homepage),
...
]
# mypackage/app.py
from fresco import FrescoApp
from mypackage import routes
app = FrescoApp()
app.include('/', routes)
Exceptions and errors¶
When you write view functions you will often need to consider error conditions, for example requests for resources that are not available or where the user doesn’t have access. Here’s an example showing how to return a 404 not found response when a requested file doesn’t exist:
import os
from fresco.exceptions import NotFound
def show_photo(img, basedir='./myphotos'):
path = os.path.normpath(os.path.join(basedir, img)) + '.jpg'
if not os.path.isfile(path):
raise NotFound()
...
Here’s a different way to return a 404 response:
if not os.path.isfile(path):
return Response.not_found()
Although both do the same thing, raising NotFound
is preferable in this case, for two reasons:
When fresco encounters a
NotFound
exception, it resumes the routing process, meaning you can write routes such as these:Route('/photos/<img:str>', GET, show_photo, basedir='./photos/family') Route('/photos/<img:str>', GET, show_photo, basedir='./photos/holiday')
If the first route fails, the second route will be tried. Only if that also raises
NotFound
will the user be shown a 404 page.You can write a new view that uses the output of the first view, without having to check the return type of the original view. For example:
def show_thumbnail(img): response = show_photo(img) image_data = ''.join(response.content) return response.replace(content=create_thumbnail(image_data))
If
show_photo
raises aNotFound
exception it will shortcut the execution ofshow_thumbnail
. This would not be the case ifshow_photo
returnedResponse.not_found()
, in which case it would try to thumbnail the HTML generated from the 404 response, which would cause your application to error.
Generating URLs¶
Fresco can generate the URL for any view you’ve defined. The urlfor
function does this:
@app.route('/hello', GET)
def sayhello():
return Response(['hello'])
# Returns 'http://localhost/hello'
urlfor(sayhello)
For view functions, you can also pass a the module and view name as a string in
the format '<package>.<module>.<view>'
. This helps you to avoid importing
the view function everywhere:
# Returns 'http://localhost/hello'
urlfor('myapp.views.sayhello')
See the page on Routing for more information.