From 979ad3af8b24792f9f21074851795835eadd4d1f Mon Sep 17 00:00:00 2001 From: Lakshmi Vyasarajan Date: Thu, 30 May 2013 00:04:11 +0530 Subject: [PATCH] Provide more informative exceptions. (Issue #204). * Better exception handling. (Issue #204) - Hyde now propagates exceptions - Hyde does not raise exceptions by default. `-x` flag is required for raising exceptions on error. - Traceback is available in the `verbose` mode * Upgraded to commando 0.3.4 * Upgraded to Jinja 2.7 --- CHANGELOG.rst | 14 +++++++++++++- README.rst | 2 +- hyde/engine.py | 9 +++++---- hyde/exceptions.py | 8 +++++++- hyde/ext/plugins/css.py | 22 ++++++++++++---------- hyde/ext/plugins/js.py | 7 +++++-- hyde/ext/plugins/meta.py | 10 ++++++---- hyde/ext/templates/jinja.py | 31 +++++++++++++++++++++---------- hyde/generator.py | 17 ++++++++++------- hyde/plugin.py | 20 +++++++++----------- hyde/server.py | 6 +++--- hyde/version.py | 2 +- requirements.txt | 6 +++--- setup.py | 6 +++--- 14 files changed, 99 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a5fbb96..a423906 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,15 @@ +Version 0.8.7a6 +============================================================ + +* Better exception handling. (Issue #204) + - Hyde now propagates exceptions + - Hyde does not raise exceptions by default. `-x` flag is required + for raising exceptions on error. + - Traceback is available in the `verbose` mode +* Upgraded to commando 0.3.4 +* Upgraded to Jinja 2.7 + + Version 0.8.7a5 ============================================================ @@ -7,7 +19,7 @@ Version 0.8.7a4 ============================================================ * Include project artifacts in sdist. (Issue #211) - - Add LICENSE, AUTHORS and CHANGELOG to MANIFEST.in + - Add LICENSE, AUTHORS and CHANGELOG to MANIFEST.in * Add "Hyde contributors to LICENSE copyright" diff --git a/README.rst b/README.rst index 0be2191..91d4dda 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Version 0.8.7a5 +Version 0.8.7a6 A brand new **hyde** ==================== diff --git a/hyde/engine.py b/hyde/engine.py index da5d286..4df557b 100644 --- a/hyde/engine.py +++ b/hyde/engine.py @@ -24,7 +24,7 @@ HYDE_LAYOUTS = "HYDE_LAYOUTS" class Engine(Application): - def __init__(self, raise_exceptions=True): + def __init__(self, raise_exceptions=False): logger = getLoggerWithConsoleHandler('hyde') super(Engine, self).__init__( raise_exceptions=raise_exceptions, @@ -34,6 +34,8 @@ class Engine(Application): @command(description='hyde - a python static website generator', epilog='Use %(prog)s {command} -h to get help on individual commands') @true('-v', '--verbose', help="Show detailed information in console") + @true('-x', '--raise-exceptions', default=False, + help="Don't handle exceptions.") @version('--version', version='%(prog)s ' + __version__) @store('-s', '--sitepath', default='.', help="Location of the hyde site") def main(self, args): @@ -43,6 +45,7 @@ class Engine(Application): like version and metadata """ sitepath = Folder(args.sitepath).fully_expanded_path + self.raise_exceptions = args.raise_exceptions return Folder(sitepath) @subcommand('create', help='Create a new hyde site.') @@ -94,7 +97,6 @@ class Engine(Application): if args.regen: self.logger.info("Regenerating the site...") incremental = False - gen.generate_all(incremental=incremental) self.logger.info("Generation complete.") @@ -147,7 +149,6 @@ class Engine(Application): publisher.publish() - def make_site(self, sitepath, config, deploy=None): """ Creates a site object from the given sitepath and the config file. @@ -155,4 +156,4 @@ class Engine(Application): config = Config(sitepath, config_file=config) if deploy: config.deploy_root = deploy - return Site(sitepath, config) + return Site(sitepath, config) \ No newline at end of file diff --git a/hyde/exceptions.py b/hyde/exceptions.py index cdc3aac..7fd8dfe 100644 --- a/hyde/exceptions.py +++ b/hyde/exceptions.py @@ -2,4 +2,10 @@ class HydeException(Exception): """ Base class for exceptions from hyde """ - pass + + @staticmethod + def reraise(message, exc_info): + _, _, tb = exc_info + raise HydeException(message), None, tb + + diff --git a/hyde/ext/plugins/css.py b/hyde/ext/plugins/css.py index 10c4c01..29caf99 100644 --- a/hyde/ext/plugins/css.py +++ b/hyde/ext/plugins/css.py @@ -10,6 +10,7 @@ from hyde.exceptions import HydeException import os import re import subprocess +import sys from fswrap import File @@ -71,8 +72,7 @@ class LessCSSPlugin(CLTransformer): afile = File(afile.path + '.less') ref = self.site.content.resource_from_path(afile.path) if not ref: - raise self.template.exception_class( - "Cannot import from path [%s]" % afile.path) + raise HydeException("Cannot import from path [%s]" % afile.path) ref.is_processable = False return self.template.get_include_statement(ref.relative_path) text = self.import_finder.sub(import_to_include, text) @@ -114,9 +114,11 @@ class LessCSSPlugin(CLTransformer): try: self.call_app(args) except subprocess.CalledProcessError: - raise self.template.exception_class( + HydeException.reraise( "Cannot process %s. Error occurred when " - "processing [%s]" % (self.app.name, resource.source_file)) + "processing [%s]" % (self.app.name, resource.source_file), + sys.exc_info()) + return target.read_all() @@ -155,7 +157,7 @@ class StylusPlugin(CLTransformer): def import_to_include(match): """ - Converts a css import statement to include statemnt. + Converts a css import statement to include statement. """ if not match.lastindex: return '' @@ -172,7 +174,7 @@ class StylusPlugin(CLTransformer): except AttributeError: include = False if not include: - raise self.template.exception_class( + raise HydeException( "Cannot import from path [%s]" % afile.path) else: ref.is_processable = False @@ -225,9 +227,10 @@ class StylusPlugin(CLTransformer): try: self.call_app(args) except subprocess.CalledProcessError: - raise self.template.exception_class( + HydeException.reraise( "Cannot process %s. Error occurred when " - "processing [%s]" % (stylus.name, resource.source_file)) + "processing [%s]" % (stylus.name, resource.source_file), + sys.exc_info()) return target.read_all() @@ -291,8 +294,7 @@ class CleverCSSPlugin(Plugin): afile = File(afile.path + '.ccss') ref = self.site.content.resource_from_path(afile.path) if not ref: - raise self.template.exception_class( - "Cannot import from path [%s]" % afile.path) + raise HydeException("Cannot import from path [%s]" % afile.path) ref.is_processable = False return self.template.get_include_statement(ref.relative_path) text = import_finder.sub(import_to_include, text) diff --git a/hyde/ext/plugins/js.py b/hyde/ext/plugins/js.py index dc5dbe6..135b2a1 100644 --- a/hyde/ext/plugins/js.py +++ b/hyde/ext/plugins/js.py @@ -3,7 +3,9 @@ JavaScript plugins """ import subprocess +import sys +from hyde.exceptions import HydeException from hyde.plugin import CLTransformer from fswrap import File @@ -127,9 +129,10 @@ class RequireJSPlugin(CLTransformer): try: self.call_app(args) except subprocess.CalledProcessError: - raise self.template.exception_class( + HydeException.reraise( "Cannot process %s. Error occurred when " - "processing [%s]" % (self.app.name, resource.source_file)) + "processing [%s]" % (self.app.name, resource.source_file), + sys.exc_info()) return target.read_all() diff --git a/hyde/ext/plugins/meta.py b/hyde/ext/plugins/meta.py index ff86b6f..d31eed0 100644 --- a/hyde/ext/plugins/meta.py +++ b/hyde/ext/plugins/meta.py @@ -8,7 +8,9 @@ from functools import partial from itertools import ifilter from operator import attrgetter import re +import sys +from hyde.exceptions import HydeException from hyde.model import Expando from hyde.plugin import Plugin from hyde.site import Node, Resource @@ -248,8 +250,9 @@ def get_tagger_sort_method(site): try: walker = getattr(content, walker) except AttributeError: - raise self.template.exception_class( - "Cannot find the sorter: %s" % sorter) + HydeException.reraise( + "Cannot find the sorter: %s" % sorter, + sys.exc_info()) return walker def walk_resources_tagged_with(node, tag): @@ -370,8 +373,7 @@ class TaggerPlugin(Plugin): Generates archives for each tag based on the given configuration. """ if not 'template' in config: - raise self.template.exception_class( - "No Template specified in tagger configuration.") + raise HydeException("No Template specified in tagger configuration.") content = self.site.content.source_folder source = Folder(config.get('source', '')) target = content.child_folder(config.get('target', 'tags')) diff --git a/hyde/ext/templates/jinja.py b/hyde/ext/templates/jinja.py index 8ab5f6f..9ec2b58 100644 --- a/hyde/ext/templates/jinja.py +++ b/hyde/ext/templates/jinja.py @@ -4,11 +4,13 @@ Jinja template utilties """ from datetime import datetime, date +import itertools import os import re -import itertools +import sys from urllib import quote, unquote +from hyde.exceptions import HydeException from hyde.model import Expando from hyde.template import HtmlWrap, Template from operator import attrgetter @@ -599,10 +601,20 @@ class HydeLoader(FileSystemLoader): # template = template.replace(os.sep, '/') logger.debug("Loading template [%s] and preprocessing" % template) - (contents, - filename, - date) = super(HydeLoader, self).get_source( + try: + (contents, + filename, + date) = super(HydeLoader, self).get_source( environment, template) + except UnicodeDecodeError: + HydeException.reraise( + "Unicode error when processing %s" % template, sys.exc_info()) + except TemplateError, exc: + HydeException.reraise('Error when processing %s: %s' % ( + template, + unicode(exc) + ), sys.exc_info()) + if self.preprocessor: resource = self.site.content.resource_from_relative_path(template) if resource: @@ -736,9 +748,11 @@ class Jinja2Template(Template): from jinja2.meta import find_referenced_templates try: ast = self.env.parse(text) - except: - logger.error("Error parsing[%s]" % path) - raise + except Exception, e: + HydeException.reraise( + "Error processing %s: \n%s" % (path, unicode(e)), + sys.exc_info()) + tpls = find_referenced_templates(ast) deps = list(self.env.globals['deps'].get('path', [])) for dep in tpls: @@ -818,9 +832,6 @@ class Jinja2Template(Template): template = self.env.get_template(resource.relative_path) out = template.render(context) except: - out = "" - logger.debug(self.env.loader.get_source( - self.env, resource.relative_path)) raise return out diff --git a/hyde/generator.py b/hyde/generator.py index 0b42e2b..0ec04c8 100644 --- a/hyde/generator.py +++ b/hyde/generator.py @@ -3,8 +3,9 @@ The generator class and related utility functions. """ -from hyde.exceptions import HydeException +from commando.util import getLoggerWithNullHandler from fswrap import File, Folder +from hyde.exceptions import HydeException from hyde.model import Context, Dependents from hyde.plugin import Plugin from hyde.template import Template @@ -12,9 +13,9 @@ from hyde.site import Resource from contextlib import contextmanager from datetime import datetime - from shutil import copymode -from commando.util import getLoggerWithNullHandler +import sys + logger = getLoggerWithNullHandler('hyde.engine') @@ -334,10 +335,12 @@ class Generator(object): try: text = self.template.render_resource(resource, context) - except Exception: - logger.error("Error occurred when" - " processing template: [%s]" % resource) - raise + except Exception, e: + HydeException.reraise("Error occurred when" + " processing template: [%s]: %s" % + (resource, repr(e)), + sys.exc_info() + ) else: text = resource.source_file.read_all() text = self.events.begin_text_resource(resource, text) or text diff --git a/hyde/plugin.py b/hyde/plugin.py index 326761d..d0839cb 100644 --- a/hyde/plugin.py +++ b/hyde/plugin.py @@ -12,6 +12,7 @@ import fnmatch import os import re import subprocess +import sys import traceback from commando.util import getLoggerWithNullHandler, load_python_object @@ -56,18 +57,19 @@ class PluginProxy(object): def __getattr__(self, method_name): if hasattr(Plugin, method_name): def __call_plugins__(*args): - # logger.debug("Calling plugin method [%s]", method_name) res = None if self.site.plugins: for plugin in self.site.plugins: if hasattr(plugin, method_name): - # logger.debug( - # "\tCalling plugin [%s]", - # plugin.__class__.__name__) checker = getattr(plugin, 'should_call__' + method_name) if checker(*args): function = getattr(plugin, method_name) - res = function(*args) + try: + res = function(*args) + except: + HydeException.reraise( + 'Error occured when calling %s' % + plugin.plugin_name, sys.exc_info()) targs = list(args) if len(targs): last = targs.pop() @@ -356,14 +358,11 @@ class CLTransformer(Plugin): app_path = discover_executable(app_path, self.site.sitepath) if app_path is None: - raise self.template.exception_class( - self.executable_not_found_message) - + raise HydeException(self.executable_not_found_message) app = File(app_path) if not app.exists: - raise self.template.exception_class( - self.executable_not_found_message) + raise HydeException(self.executable_not_found_message) return app @@ -418,7 +417,6 @@ class CLTransformer(Plugin): (args[0], unicode(args[1:]))) return subprocess.check_output(args) except subprocess.CalledProcessError, error: - self.logger.error(traceback.format_exc()) self.logger.error(error.output) raise diff --git a/hyde/server.py b/hyde/server.py index 4b7d8f7..0223b3b 100644 --- a/hyde/server.py +++ b/hyde/server.py @@ -162,7 +162,7 @@ class HydeWebServer(HTTPServer): except Exception, exception: logger.error('Error occured when regenerating the site [%s]' % exception.message) - logger.error(traceback.format_exc()) + logger.debug(traceback.format_exc()) def generate_node(self, node): """ @@ -180,7 +180,7 @@ class HydeWebServer(HTTPServer): logger.error( 'Error [%s] occured when generating the node [%s]' % (repr(exception), node)) - logger.error(traceback.format_exc()) + logger.debug(traceback.format_exc()) def generate_resource(self, resource): """ @@ -199,4 +199,4 @@ class HydeWebServer(HTTPServer): logger.error( 'Error [%s] occured when serving the resource [%s]' % (repr(exception), resource)) - logger.error(traceback.format_exc()) + logger.debug(traceback.format_exc()) diff --git a/hyde/version.py b/hyde/version.py index 3f3439d..936b816 100644 --- a/hyde/version.py +++ b/hyde/version.py @@ -2,4 +2,4 @@ """ Handles hyde version. """ -__version__ = '0.8.7a5' +__version__ = '0.8.7a6' diff --git a/requirements.txt b/requirements.txt index d8a4a6a..193c1eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ fswrap==0.1.1 -commando==0.3.2a +commando==0.3.4 PyYAML==3.10 Markdown==2.3.1 -MarkupSafe==0.15 +MarkupSafe==0.18 Pygments==1.6 typogrify==2.0.0 -Jinja2==2.6 +Jinja2==2.7 diff --git a/setup.py b/setup.py index 92653f2..195b1a6 100644 --- a/setup.py +++ b/setup.py @@ -117,13 +117,13 @@ setup(name=PROJECT, requires=['python (>= 2.7)'], install_requires=( 'fswrap==0.1.1', - 'commando==0.3.2a', + 'commando==0.3.4', 'PyYAML==3.10', 'Markdown==2.3.1', - 'MarkupSafe==0.15', + 'MarkupSafe==0.18', 'Pygments==1.6', 'typogrify==2.0.0', - 'Jinja2==2.6' + 'Jinja2==2.7' ), tests_require=( 'nose', 'mock'