So far I've found it impossible to produce usable tracebacks when Mako templates aren't coded correctly.
Is there any way to debug templates besides iterating for every line of code?
Mako actually provides a VERY nice way to track down errors in a template:
from mako import exceptions
try:
template = lookup.get_template(uri)
print template.render()
except:
print exceptions.html_error_template().render()
Looking at the Flask-Mako source, I found an undocumented configuration parameter called MAKO_TRANSLATE_EXCEPTIONS.
Set this to False in your Flask app config and you'll get nice exceptions bubbling up from the template. This accomplishes the same thing as #Mariano suggested, without needing to edit the source. Apparently, this parameter was added after Mariano's answer.
I break them down into pieces, and then reassemble the pieces when I've found the problem.
Not good, but it's really hard to tell what went wrong in a big, complex template.
My main frustration with Mako was that it was hard to see what was happening in the template. As the template code is a runnable object that is in-memory, no debugger can look into it.
One solution is to write the template code to file, and re-run the template using this file as a standard python module. Then you can debug to your hearts content.
An example:
import sys
from mako import exceptions, template
from mako.template import DefTemplate
from mako.runtime import _render
<Do Great Stuff>
try:
template.render(**arguments))
except:
# Try to re-create the error using a proper file template
# This will give a clearer error message.
with open('failed_template.py', 'w') as out:
out.write(template._code)
import failed_template
data = dict(callable=failed_template.render_body, **arguments)
try:
_render(DefTemplate(template, failed_template.render_body),
failed_template.render_body,
[],
data)
except:
msg = '<An error occurred when rendering template for %s>\n'%arguments
msg += exceptions.text_error_template().render()
print(msg, file=sys.stderr)
raise
Using flask_mako, I find it's easier to skip over the TemplateError generation and just pass up the exception. I.e. in flask_mako.py, comment out the part that makes the TemplateError and just do a raise:
def _render(template, context, app):
"""Renders the template and fires the signal"""
app.update_template_context(context)
try:
rv = template.render(**context)
template_rendered.send(app, template=template, context=context)
return rv
except:
#translated = TemplateError(template)
#raise translated
raise
}
Then you'll see a regular python exception that caused the problem along with line numbers in the template.
Combining the two top answers with my own special sauce:
from flask.ext.mako import render_template as render_template_1
from mako import exceptions
app.config['MAKO_TRANSLATE_EXCEPTIONS'] = False # seems to be necessary
def render_template(*args, **kwargs):
kwargs2 = dict(**kwargs)
kwargs2['config'] = app.config # this is irrelevant, but useful
try:
return render_template_1(*args, **kwargs2)
except:
if app.config.get('DEBUG'):
return exceptions.html_error_template().render()
raise
It wraps the stock "render_template" function:
catch exceptions, and
if debugging, render a backtrace
if not debugging, raise the exception again so it will be logged
make config accessible from the page (irrelevant)
Related
I need to validate a Flask rule in a REST API that is built using Flask-Rebar. I've tried the following method:
#registry.handles(
rule='/horses/<int:horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
return {"horse_id": horse_id}
This is using the <int:my_var> syntax as specified here. When entering an integer value as the horse_id everything works correctly. The issue comes from entering a non-integer value, i.e. a; this throws a 404 Not Found status code when I was expecting a 400 Bad Request.
I don't think I can use any of the marshalling schemas for this so I'm unsure as to what to try next, any help is appreciated.
Thanks,
Adam
This is how Flask/Werkzeug behaves, so its a bit beyond Flask-Rebar's control. That is, the following will also return a 404 for /horses/a:
app = Flask(__name__)
#app.route('/horses/<int:horse_id>')
def getHorseById(horse_id):
return str(horse_id)
With that, here are some workarounds:
(1) Custom URL converter: http://flask.pocoo.org/docs/1.0/api/#flask.Flask.url_map
This would look something like:
import flask
from werkzeug.routing import BaseConverter
class StrictIntegerConverter(BaseConverter):
def to_python(self, value):
try:
return int(value)
except ValueError:
flask.abort(400)
app = flask.Flask(__name__)
app.url_map.converters['strict_integer'] = StrictIntegerConverter
#registry.handles(
rule='/horses/<strict_integer:horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
return {'horse_id': horse_id}
However, routing is done outside of application context, so we can't use flask.jsonify nor Flask-Rebar's errors to raise nice JSON errors.
(2) Check the type inside the handler function
from flask_rebar.errors import BadRequest
#registry.handles(
rule='/horses/<horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
try:
horse_id = int(horse_id)
except ValueError:
raise BadRequest('horse_id must be an integer')
return {'horse_id': horse_id}
This is a little less elegant, but it works.
The Swagger document will default to a string type for the horse_id parameter, but we can work around that as well:
from werkzeug.routing import UnicodeConverter
class StrDocumentedAsIntConverter(UnicodeConverter):
pass
app.url_map.converters['str_documented_as_int'] = StrDocumentedAsIntConverter
registry.swagger_generator.register_flask_converter_to_swagger_type('str_documented_as_int', 'int')
#registry.handles(rule='/horses/<str_documented_as_int:horse_id>')
...
(3) Accept Flask/Werkzeug behavior
Depending on how badly you need 400 instead of 404, it might be most practical to do nothing and just give in to how Flask/Werkzeug do it.
I've uploaded a json from the user and now I'm trying to compare that json to a schema using the jsonschema validator. I'm getting an error, ValidationError: is not of type u'object'
Failed validating u'type' in schema
This is my code so far:
from __future__ import unicode_literals
from django.shortcuts import render, redirect
import jsonschema
import json
import os
from django.conf import settings
#File to store all the parsers
def jsonVsSchemaParser(project, file):
baseProjectURL = 'src\media\json\schema'
projectSchema = project.lower()+'.schema'
projectPath = os.path.join(baseProjectURL,projectSchema)
filePath = os.path.join(settings.BASE_DIR,'src\media\json', file)
actProjectPath = os.path.join(settings.BASE_DIR,projectPath)
print filePath, actProjectPath
schemaResponse = open(actProjectPath)
schema = json.load(schemaResponse)
response = open(filePath)
jsonFile = json.load(response)
jsonschema.validate(jsonFile, schema)
I'm trying to do something similar to this question except instead of using a url I'm using my filepath.
Also I'm using python 2.7 and Django 1.11 if that is helpful at all.
Also I'm pretty sure I don't have a problem with my filepaths because I printed them and it outputted what I was expecting. I also know that my schema and json can be read by jsonschema since I used it on the command line as well.
EDIT: that validation error seemed to be a fluke. the actual validation error I'm consistently getting is "-1 is not of type u'string'". The annoying thing is that's supposed to be like that. It is wrong that sessionid isn't a string but I want that to be handled by the jsonschema but I don't want my validation errors to be given in this format: . What I want to do is collect all validation errors in an array and then post it to the user in the next page.
I just ended up putting a try-catch around my validate method. Here's what it looks like:
validationErrors = []
try:
jsonschema.validate(jsonFile, schema)
except jsonschema.exceptions.ValidationError as error:
validationErrors.append(error)
EDIT: This solution only works if you have one error because after the validation error is called it breaks out of the validate method. In order to present every error you need to use lazy validation. This is how it looks in my code if you need another example:
v = jsonschema.Draft4Validator(schema)
for error in v.iter_errors(jsonFile):
validationErrors.append(error)
✓ try-except-else-finally statement is a great way to catch and handle exceptions(Run time errors) in Python.
✓So if you want to catch and store Exceptions in an array then the great solution for you is to use try-except statement. In this way you can catch and store in any data structure like lists etc. and your program with continue with its execution, it will not terminate.
✓ Below is a modified code where I have used a for loop which catches error 5 times and stores in list.
validationErrors = []
for i in range(5):
try:
jsonschema.validate(jsonFile, schema)
except jsonschema.exceptions.ValidationError as error:
validationErrors.append(error)
✓ Finally, you can have a look at the below code sample where I have stored ZeroDivisionError and it's related string message in 2 different lists by iterating over a for loop 5 times.
You can use the 2nd list ZeroDivisionErrorMessagesList to pass to template, if you want to print messages on web page (if you want). You can use 1st also.
ZeroDivisionErrorsList = [];
ZeroDivisionErrorMessagesList = list(); # list() is same as [];
for i in range(5):
try:
a = 10 / 0; # it will raise exception
print(a);. # it will not execute
except ZeroDivisionError as error:
ZeroDivisionErrorsList.append(error)
ZeroDivisionErrorMessagesList.append(str(error))
print(ZeroDivisionErrorsList);
print(); # new line
print(ZeroDivisionErrorMessagesList);
» Output:
[ZeroDivisionError('division by zero',),
ZeroDivisionError('division by zero',),
ZeroDivisionError('division by zero',),
ZeroDivisionError('division by zero',),
ZeroDivisionError('division by zero',)]
['division by zero', 'division by zero', 'division by zero', 'division by zero', 'division by zero']
In flask, I can do this:
render_template("foo.html", messages={'main':'hello'})
And if foo.html contains {{ messages['main'] }}, the page will show hello. But what if there's a route that leads to foo:
#app.route("/foo")
def do_foo():
# do some logic here
return render_template("foo.html")
In this case, the only way to get to foo.html, if I want that logic to happen anyway, is through a redirect:
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
return redirect("/foo", messages={"main":"Condition failed on page baz"})
# above produces TypeError: redirect() got an unexpected keyword argument 'messages'
So, how can I get that messages variable to be passed to the foo route, so that I don't have to just rewrite the same logic code that that route computes before loading it up?
You could pass the messages as explicit URL parameter (appropriately encoded), or store the messages into session (cookie) variable before redirecting and then get the variable before rendering the template. For example:
from flask import session, url_for
def do_baz():
messages = json.dumps({"main":"Condition failed on page baz"})
session['messages'] = messages
return redirect(url_for('.do_foo', messages=messages))
#app.route('/foo')
def do_foo():
messages = request.args['messages'] # counterpart for url_for()
messages = session['messages'] # counterpart for session
return render_template("foo.html", messages=json.loads(messages))
(encoding the session variable might not be necessary, flask may be handling it for you, but can't recall the details)
Or you could probably just use Flask Message Flashing if you just need to show simple messages.
I found that none of the answers here applied to my specific use case, so I thought I would share my solution.
I was looking to redirect an unauthentciated user to public version of an app page with any possible URL params. Example:
/app/4903294/my-great-car?email=coolguy%40gmail.com to
/public/4903294/my-great-car?email=coolguy%40gmail.com
Here's the solution that worked for me.
return redirect(url_for('app.vehicle', vid=vid, year_make_model=year_make_model, **request.args))
Hope this helps someone!
I'm a little confused. "foo.html" is just the name of your template. There's no inherent relationship between the route name "foo" and the template name "foo.html".
To achieve the goal of not rewriting logic code for two different routes, I would just define a function and call that for both routes. I wouldn't use redirect because that actually redirects the client/browser which requires them to load two pages instead of one just to save you some coding time - which seems mean :-P
So maybe:
def super_cool_logic():
# execute common code here
#app.route("/foo")
def do_foo():
# do some logic here
super_cool_logic()
return render_template("foo.html")
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
super_cool_logic()
return render_template("foo.html", messages={"main":"Condition failed on page baz"})
I feel like I'm missing something though and there's a better way to achieve what you're trying to do (I'm not really sure what you're trying to do)
You can however maintain your code and simply pass the variables in it separated by a comma: if you're passing arguments, you should rather use render_template:
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
return render_template("/foo", messages={"main":"Condition failed on page baz"})
I'm working on a CMS in Python that uses reStructuredText (via docutils) to format content. Alot of my content is imported from other sources and usually comes in the form of unformatted text documents. reST works great for this because it makes everything look pretty sane by default.
One problem I am having, however, is that I get warnings dumped to stderr on my webserver and injected into my page content. For example, I get warnings like the following on my web page:
System Message: WARNING/2 (, line 296); backlink
My question is: How do I suppress, disable, or otherwise re-direct these warnings?
Ideally, I'd love to write these out to a log file, but if someone can just tell me how to turn off the warnings from being injected into my content then that would be perfect.
The code that's responsible for parsing the reST into HTML:
from docutils import core
import reSTpygments
def reST2HTML( str ):
parts = core.publish_parts(
source = str,
writer_name = 'html')
return parts['body_pre_docinfo'] + parts['fragment']
def reST2HTML( str ):
parts = core.publish_parts(
source = str,
writer_name = 'html',
settings_overrides={'report_level':'quiet'},
)
return parts['body_pre_docinfo'] + parts['fragment']
It seems the report_level accept string is an old version. Now, the below is work for me.
import docutils.core
import docutils.utils
from pathlib import Path
shut_up_level = docutils.utils.Reporter.SEVERE_LEVEL + 1
docutils.core.publish_file(
source_path=Path(...), destination_path=Path(...),
settings_overrides={'report_level': shut_up_level},
writer_name='html')
about level
# docutils.utils.__init__.py
class Reporter(object):
# system message level constants:
(DEBUG_LEVEL,
INFO_LEVEL,
WARNING_LEVEL,
ERROR_LEVEL,
SEVERE_LEVEL) = range(5)
...
def system_message(self, level, message, *children, **kwargs):
...
if self.stream and (level >= self.report_level # self.report_level was set by you. (for example, shut_up_level)
or self.debug_flag and level == self.DEBUG_LEVEL
or level >= self.halt_level):
self.stream.write(msg.astext() + '\n')
...
return msg
According to the above code, you know that you can assign the self.report_level (i.e. settings_overrides={'report_level': ...}) let the warning not show.
and I set it to SERVER_LEVEL+1, so it will not show any error. (you can set it according to your demand.)
I've written a Django application which interacts with a third-party API (Disqus, although this detail is unimportant) via a Python wrapper. When the service is unavailable, the Python wrapper raises an exception.
The best way for the application to handle such exceptions is to suppress them so that the rest of the page's content can still be displayed to the user. The following works fine.
try:
somemodule.method_that_may_raise_exception(args)
except somemodule.APIError:
pass
Certain views contain several such calls. Is wrapping each call in try/except the best way to suppress possible exceptions?
Making API calls from views is not so good idea. You should probably create another module, that does the job.
ie. when I make Facebook apps I create publish.py file to store all "publish to live stream" calls. Functions in that module are named based on when they should be called. Ie.:
# publish.py
def authorise_application(user):
# API call "User joined app."
def post_anwser(anwser):
# API call "User posted anwser to quiz".
Then your views are very clean:
# views.py
def post_anwser(request):
...
if form.is_valid():
form.save()
publish.post_anwser(form.instance)
When you have your code organised that way, you can create decorator for ignoring exceptions:
# publish.py
def ignore_api_error(fun):
def res(*args, **kwargs):
try:
return fun(*args, **kwargs):
except someservice.ApiError:
return None
return res
#ignore_api_error
def authorised_application(user):
# API call "User joined app."
#ignore_api_error
def posted_anwser(user, anwser):
# API call "User posted anwser to quiz".
Also you can create function, that is not "ignored" by default, and add ignore code in view:
# publish.py
def some_function(user, message):
pass
# views.py
def my_view():
...
publish.ignore_api_error(publish.some_function)(user, message)
...
Certain views contain several such calls. Is wrapping each call in try/except the best way to suppress possible exceptions?
You can wrap the API call inside another function. Say:
def make_api_call(*args, **kwargs):
try:
return somemodule.method_that_may_raise_exception(*args, **kwargs)
except somemodule.APIError:
log.warn("....")
This function can be called instead of the try/except block in each view. This will at least serve to reduce the number of lines of code you write and provide a common place for handling such exceptions.
Update
#Yorirou is correct. Changing code to add this good practice.