Return-Path: X-Original-To: apmail-incubator-allura-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-allura-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id DB72810BB1 for ; Fri, 7 Jun 2013 23:41:57 +0000 (UTC) Received: (qmail 43475 invoked by uid 500); 7 Jun 2013 23:41:57 -0000 Delivered-To: apmail-incubator-allura-commits-archive@incubator.apache.org Received: (qmail 43389 invoked by uid 500); 7 Jun 2013 23:41:57 -0000 Mailing-List: contact allura-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: allura-dev@incubator.apache.org Delivered-To: mailing list allura-commits@incubator.apache.org Received: (qmail 43186 invoked by uid 99); 7 Jun 2013 23:41:57 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 07 Jun 2013 23:41:57 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 597408A0975; Fri, 7 Jun 2013 23:41:57 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tvansteenburgh@apache.org To: allura-commits@incubator.apache.org Date: Fri, 07 Jun 2013 23:42:12 -0000 Message-Id: <2520e1b332144c20b239e3bed9aa23ae@git.apache.org> In-Reply-To: <1ccbe82e88c14f838440761f0be101e9@git.apache.org> References: <1ccbe82e88c14f838440761f0be101e9@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [17/27] git commit: [#6325] pep8 of app_cfg [#6325] pep8 of app_cfg Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/2d39c619 Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2d39c619 Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2d39c619 Branch: refs/heads/db/6276 Commit: 2d39c619a7b21edbee64e9af401edd4c92205810 Parents: bf6db7f Author: Nicholas Bollweg (Nick) Authored: Tue Feb 7 18:00:26 2012 -0500 Committer: Cory Johns Committed: Tue Jun 4 20:51:31 2013 +0000 ---------------------------------------------------------------------- Allura/allura/config/app_cfg.py | 22 +-- Allura/allura/lib/package_path_loader.py | 229 +++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2d39c619/Allura/allura/config/app_cfg.py ---------------------------------------------------------------------- diff --git a/Allura/allura/config/app_cfg.py b/Allura/allura/config/app_cfg.py index 86c65b4..854ad0b 100644 --- a/Allura/allura/config/app_cfg.py +++ b/Allura/allura/config/app_cfg.py @@ -30,9 +30,6 @@ convert them into boolean, for example, you should use the setting = asbool(global_conf.get('the_setting')) """ -import logging -import pkg_resources - import tg import jinja2 import pylons @@ -45,8 +42,8 @@ import ew import allura # needed for tg.configuration to work from allura.lib import app_globals, helpers +from allura.lib.package_path_loader import PackagePathLoader -log = logging.getLogger(__name__) class ForgeConfig(AppConfig): @@ -54,13 +51,12 @@ class ForgeConfig(AppConfig): AppConfig.__init__(self) self.root_controller = root_controller self.package = allura - self.renderers = [ 'json', 'genshi', 'jinja' ] + self.renderers = ['json', 'genshi', 'mako', 'jinja'] self.default_renderer = 'genshi' self.use_sqlalchemy = False self.use_toscawidgets = True self.use_transaction_manager = False - # self.handle_status_codes = [ 403, 404 ] - self.handle_status_codes = [ 403, 404 ] + self.handle_status_codes = [403, 404] self.disable_request_extensions = True def after_init_config(self): @@ -108,6 +104,7 @@ class ForgeConfig(AppConfig): config['pylons.strict_c'] = True self.render_functions.jinja = tg.render.render_jinja + class JinjaEngine(ew.TemplateEngine): @property @@ -129,15 +126,4 @@ class JinjaEngine(ew.TemplateEngine): text = template.render(**context) return literal(text) -class PackagePathLoader(jinja2.BaseLoader): - - def __init__(self): - self.fs_loader = jinja2.FileSystemLoader(['/']) - - def get_source(self, environment, template): - package, path = template.split(':') - filename = pkg_resources.resource_filename(package, path) - return self.fs_loader.get_source(environment, filename) - - base_config = ForgeConfig() http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2d39c619/Allura/allura/lib/package_path_loader.py ---------------------------------------------------------------------- diff --git a/Allura/allura/lib/package_path_loader.py b/Allura/allura/lib/package_path_loader.py new file mode 100644 index 0000000..a88a061 --- /dev/null +++ b/Allura/allura/lib/package_path_loader.py @@ -0,0 +1,229 @@ +''' +A Jinja template loader which allows for: + - dotted-notation package loading + - search-path-based overriding of same + +## Dotted notation +- Allow a Tool implementer to use a dotted-notation module name + (as occuring in the PYTONPATH), then the given path within the + module: + + @expose('jinja:module.name:path/within/module.html>') + + e.g. + + @expose('jinja:allura:templates/repo/file.html') + +## Overriding dotted notation +Allow a Tool implementer to override the theme baseline (or any +other Tool's) templates. This can be lighter-weight than subclassing +allura.plugin.ThemeProvider, plus will allow for more fine-grained +changes. + +This will also override `extends` and `import` Jinja tags. + +This approach uses a: + +- setup.py entry point to a class with... +- _magic_ files and... +- (optionally) a class property to specify ordering + +### File Structure for Overriding dotted notation +For the examples, assume the following directory structure: + + NewTool/ + |- setup.py <- entry point specified here + |- newtool/ + |- app.py <- entry point target here + |- templates/ + |- index.html <- Tool's regular templates + |- allura/ <- magic directory named after module + |- templates/ + |- repo/ + |- file.html <- actual template + +To override the above example, a Tool implementer would +add the following line to their Tool's setup.py: + + [theme.override] + newtool = newtool.app:NewToolApp + +Then, in the neighbor path (see below) for the file containing the +Tool class, add the following path/file: + + templates/allura/templates/repo/file.html + +The template will be overridden. Note that after changing +setup.py, it would be required to re-initialize with setuptools: + + python setup.py develop + +### Specifying search path order with template_path_rules +If a highly specific ordering is required, such as if multiple Tools +are trying to override the same template, the entry point target +class can also contain a class property template_path_rules: + + class NewToolApp(Application): + template_path_rules = [ + ['>', 'old-tool'], + ] + +Each rule specifies a postioner and an entry point or "signpost". +If no rule is provided, the default is ['>', 'allura']. + +The "signposts" are: + +- site-theme +- allura (you probably shouldn't do this) +- project-theme NOT IMPLEMENTED +- tool-theme NOT IMPLEMENTED + +The positioners are: +- > + - This overrider will be found BEFORE the specified entry point +- < + - This overrider will be found AFTER the specified entry point... not + exectly sure why you would use this. +- = + - This will replace one of the "signpost" entry points... if multiple + entry points try to do this, the result is undefined. + TODO: Support multiple partial themes +''' +import pkg_resources +import os + +import jinja2 + + +class PackagePathLoader(jinja2.BaseLoader): + ''' + Implements the following extensions to the BaseLoader for locating + templates: dotted-notation module-based template loading, and overriding + the same with other Tools. + ''' + def __init__(self, override_entrypoint='allura.theme.override', + default_paths=None): + ''' + Set up initial values... defaults are for Allura. + ''' + # TODO: How does one handle project-theme? + if default_paths is None: + default_paths = [ + #['projec-theme', None], + ['site-theme', None], + ['allura', '/'], + ] + + self.override_entrypoint = override_entrypoint + self.default_paths = default_paths + + # Finally instantiate the loader + self.fs_loader = jinja2.FileSystemLoader(self.init_paths()) + + def init_paths(self): + ''' + Set up the setuptools entry point-based paths. + ''' + paths = self.default_paths[:] + + ''' + Iterate through the overriders. + TODO: Can this be moved to allura.app_globals.Globals, or is this + executed before that is available? + ''' + epoints = pkg_resources.iter_entry_points(self.override_entrypoint) + for epoint in epoints: + overrider = epoint.load() + # Get the path of the module + tmpl_path = pkg_resources.resource_filename( + overrider.__module__, + "" + ) + # Default insert position is right before allura(/) + insert_position = len(paths) - 1 + + rules = getattr(overrider, 'template_path_rules', []) + + # Check each of the rules for this overrider + for direction, signpost in rules: + sp_location = None + + # Find the signpost + try: + sp_location = [path[0] for path in paths].index(signpost) + except ValueError: + # Couldn't find it, hope they specified another one, or + # that the default is ok. + continue + + if direction == '=': + # Set a signpost. Behavior if already set is undetermined, + # as entry point ordering is undetermined + paths[sp_location][1] = tmpl_path + # already inserted! our work is done here + insert_position = None + break + elif direction == '>': + # going to put it right before the signpost + insert_position = min(sp_location, insert_position) + elif direction == '<': + # going to put it right after the signpost + insert_position = min(sp_location + 1, insert_position) + else: + # don't know what that is! + raise Exception('Unknown template path rule in %s: %s' % ( + overrider, direction)) + + # in the case that we've already replaced a signpost, carry on + if insert_position is not None: + # TODO: wouldn't OrderedDict be better? the allura.lib one + # doesn't support ordering like the markdown one + paths.insert(insert_position, (epoint.name, tmpl_path)) + + # Get rid of None paths... not useful + return [path for name, path in paths if path is not None] + + def get_source(self, environment, template): + ''' + Returns the source for jinja2 rendered templates. Can understand... + - path/to/template.html + - module:path/to/template.html + ''' + package, path = None, None + src = None + bits = template.split(':') + if len(bits) == 2: + # splitting out the Python module name from the template string... + # the default allura behavior + package, path = template.split(':') + # TODO: is there a better way to do this? + path_fragment = os.path.join('templates', package, path) + elif len(bits) == 1: + # TODO: is this even useful? + path = bits[0] + path_fragment = os.path.join('templates', path) + else: + raise Exception('malformed template path') + + # look in all of the customized search locations... + try: + src = self.fs_loader.get_source(environment, path_fragment) + except Exception: + # no Tool implemented an override... not even sure if this will + # throw an error, but we're ready for it! + pass + + # ...but if you don't find anything, fall back to the explicit package + # approach + if src is None and package is not None: + # gets the absolute filename of the template + filename = pkg_resources.resource_filename(package, path) + # get the filename relative to the fs root (/).. if this fails + # this error is not caught, so should get propagated normally + src = self.fs_loader.get_source(environment, filename) + elif src is None: + raise Exception(('Template %s not found in search path ' + + 'and no module specified') % ( + path, + )) + return src