From 6342355b367adf59619aef5d386c96d4ec056186 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 13 Nov 2010 23:55:50 -0500 Subject: path->info --- pyramid/chameleon_text.py | 4 ++-- pyramid/chameleon_zpt.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py index 4b97b2ba5..91f5c279f 100644 --- a/pyramid/chameleon_text.py +++ b/pyramid/chameleon_text.py @@ -39,8 +39,8 @@ class TextTemplateFile(TemplateFile): super(TextTemplateFile, self).__init__(filename, parser, format, doctype, **kwargs) -def renderer_factory(path): - return renderers.template_renderer_factory(path, TextTemplateRenderer) +def renderer_factory(info): + return renderers.template_renderer_factory(info, TextTemplateRenderer) class TextTemplateRenderer(object): implements(ITemplateRenderer) diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index d5cc20262..172899d41 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -21,8 +21,8 @@ from pyramid import renderers from pyramid.settings import get_settings from pyramid.threadlocal import get_current_registry -def renderer_factory(path): - return renderers.template_renderer_factory(path, ZPTTemplateRenderer) +def renderer_factory(info): + return renderers.template_renderer_factory(info, ZPTTemplateRenderer) class ZPTTemplateRenderer(object): implements(ITemplateRenderer) -- cgit v1.2.3 From 633700e4c820ba8d0d1652cd799f7119b7a07632 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 14 Nov 2010 00:15:51 -0500 Subject: remove template property, it's not part of the interface --- pyramid/mako_templating.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index bf51257a0..7e9ae236f 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -81,12 +81,8 @@ class MakoLookupTemplateRenderer(object): self.lookup = lookup def implementation(self): - return self.template - - @property - def template(self): return self.lookup.get_template(self.path) - + def __call__(self, value, system): context = system.pop('context', None) if context is not None: @@ -98,7 +94,7 @@ class MakoLookupTemplateRenderer(object): system.update(value) except (TypeError, ValueError): raise ValueError('renderer was passed non-dictionary as value') - template = self.template + template = self.implementation() if def_name is not None: template = template.get_def(def_name) result = template.render_unicode(**system) -- cgit v1.2.3 From 4a1d0e5fb4d94ca657f85d0ec0f9011485c012a5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 14 Nov 2010 00:19:51 -0500 Subject: hold a lock while we mutate the registry: it's not safe for more than one thread to mutate it at runtime --- pyramid/mako_templating.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index 7e9ae236f..b63009e2c 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -1,4 +1,5 @@ import os +import threading from zope.interface import implements from zope.interface import Interface @@ -51,6 +52,8 @@ class PkgResourceTemplateLookup(TemplateLookup): return TemplateLookup.get_template(self, uri) +registry_lock = threading.Lock() + def renderer_factory(info): path = info.name registry = info.registry @@ -70,7 +73,12 @@ def renderer_factory(info): module_directory=module_directory, input_encoding=input_encoding, filesystem_checks=reload_templates) - registry.registerUtility(lookup, IMakoLookup) + registry_lock.acquire() + try: + registry.registerUtility(lookup, IMakoLookup) + finally: + registry_lock.release() + return MakoLookupTemplateRenderer(path, lookup) -- cgit v1.2.3 From 7b8d650f15720283893bd214368cf5d97a6af7ce Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 03:55:48 -0500 Subject: - Internal: Chameleon template renderers now accept two arguments: ``path`` and ``lookup``. ``Lookup`` will be an instance of a lookup class which supplies (late-bound) arguments for debug, reload, and translate. Any third-party renderers which use (the non-API) function ``pyramid.renderers.template_renderer_factory`` will need to adjust their implementations to obey the new callback argument list. This change was to kill off inappropriate use of threadlocals. --- CHANGES.txt | 8 ++ pyramid/chameleon_text.py | 24 ++---- pyramid/chameleon_zpt.py | 29 +++---- pyramid/interfaces.py | 7 ++ pyramid/renderers.py | 150 ++++++++++++++++++++++------------- pyramid/tests/test_chameleon_text.py | 50 +++++++----- pyramid/tests/test_chameleon_zpt.py | 56 ++++++++----- pyramid/tests/test_renderers.py | 32 +++++--- 8 files changed, 209 insertions(+), 147 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e7c42d659..b9f6a69c7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -68,6 +68,14 @@ Behavior Differences a ``registry`` attribute on the ZCML context (kill off use of threadlocals). +- Internal: Chameleon template renderers now accept two arguments: ``path`` + and ``lookup``. ``Lookup`` will be an instance of a lookup class which + supplies (late-bound) arguments for debug, reload, and translate. Any + third-party renderers which use (the non-API) function + ``pyramid.renderers.template_renderer_factory`` will need to adjust their + implementations to obey the new callback argument list. This change was to + kill off inappropriate use of threadlocals. + 1.0a2 (2010-11-09) ================== diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py index 91f5c279f..1f31f3add 100644 --- a/pyramid/chameleon_text.py +++ b/pyramid/chameleon_text.py @@ -21,13 +21,10 @@ except ImportError: # pragma: no cover pass from pyramid.interfaces import ITemplateRenderer -from pyramid.interfaces import IChameleonTranslate from pyramid.decorator import reify from pyramid import renderers from pyramid.path import caller_package -from pyramid.settings import get_settings -from pyramid.threadlocal import get_current_registry class TextTemplateFile(TemplateFile): default_parser = Parser() @@ -44,30 +41,19 @@ def renderer_factory(info): class TextTemplateRenderer(object): implements(ITemplateRenderer) - def __init__(self, path): + def __init__(self, path, lookup): self.path = path + self.lookup = lookup @reify # avoid looking up reload_templates before manager pushed def template(self): if sys.platform.startswith('java'): # pragma: no cover raise RuntimeError( 'Chameleon templates are not compatible with Jython') - settings = get_settings() - debug = False - auto_reload = False - if settings: - # using .get here is a strategy to be kind to old *tests* rather - # than being kind to any existing production system - auto_reload = settings.get('reload_templates') - debug = settings.get('debug_templates') - reg = get_current_registry() - translate = None - if reg is not None: - translate = reg.queryUtility(IChameleonTranslate) return TextTemplateFile(self.path, - auto_reload=auto_reload, - debug=debug, - translate=translate) + auto_reload=self.lookup.auto_reload, + debug=self.lookup.debug, + translate=self.lookup.translate) def implementation(self): return self.template diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index 172899d41..6aae59a87 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -1,4 +1,5 @@ import sys +import threading from zope.interface import implements @@ -12,44 +13,32 @@ except ImportError: # pragma: no cover def __init__(self, *arg, **kw): raise ImportError, exc, tb -from pyramid.interfaces import IChameleonTranslate from pyramid.interfaces import ITemplateRenderer from pyramid.decorator import reify from pyramid.path import caller_package from pyramid import renderers -from pyramid.settings import get_settings -from pyramid.threadlocal import get_current_registry -def renderer_factory(info): +registry_lock = threading.Lock() + +def renderer_factory(info, lock=registry_lock): return renderers.template_renderer_factory(info, ZPTTemplateRenderer) class ZPTTemplateRenderer(object): implements(ITemplateRenderer) - def __init__(self, path): + def __init__(self, path, lookup): self.path = path + self.lookup = lookup @reify # avoid looking up reload_templates before manager pushed def template(self): if sys.platform.startswith('java'): # pragma: no cover raise RuntimeError( 'Chameleon templates are not compatible with Jython') - settings = get_settings() - debug = False - auto_reload = False - if settings: - # using .get here is a strategy to be kind to old *tests* rather - # than being kind to any existing production system - auto_reload = settings.get('reload_templates') - debug = settings.get('debug_templates') - reg = get_current_registry() - translate = None - if reg is not None: - translate = reg.queryUtility(IChameleonTranslate) return PageTemplateFile(self.path, - auto_reload=auto_reload, - debug=debug, - translate=translate) + auto_reload=self.lookup.auto_reload, + debug=self.lookup.debug, + translate=self.lookup.translate) def implementation(self): return self.template diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 9835f8f7d..47bccf71f 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -392,6 +392,13 @@ class IPackageOverrides(Interface): # traversalwrapper) VH_ROOT_KEY = 'HTTP_X_VHM_ROOT' +class IChameleonLookup(Interface): + translate = Attribute('IChameleonTranslate object') + debug = Attribute('The ``debug_templates`` setting for this application') + auto_reload = Attribute('The ``reload_templates`` setting for this app') + def __call__(self, info): + """ Return an ITemplateRenderer based on IRendererInfo ``info`` """ + class IChameleonTranslate(Interface): """ Internal interface representing a chameleon translate function """ def __call__(msgid, domain=None, mapping=None, context=None, diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 0c2a7c5db..94b58cf92 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -4,6 +4,8 @@ import threading from zope.interface import implements +from pyramid.interfaces import IChameleonLookup +from pyramid.interfaces import IChameleonTranslate from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IResponseFactory @@ -150,66 +152,102 @@ def string_renderer_factory(info): # utility functions, not API -# Lock to prevent simultaneous registry writes; used in -# template_renderer_factory. template_renderer_factory may be called -# at runtime, from more than a single thread. -registry_lock = threading.Lock() - -def template_renderer_factory(info, impl, lock=registry_lock): - spec = info.name - reg = info.registry - package = info.package - - isabs = os.path.isabs(spec) - - if (not isabs) and (not ':' in spec) and package: - # relative resource spec - if not isabs: - pp = package_path(package) - spec = os.path.join(pp, spec) - spec = resource_spec_from_abspath(spec, package) - - if os.path.isabs(spec): - # 'spec' is an absolute filename - if not os.path.exists(spec): - raise ValueError('Missing template file: %s' % spec) - renderer = reg.queryUtility(ITemplateRenderer, name=spec) - if renderer is None: - renderer = impl(spec) - # cache the template - try: - lock.acquire() - reg.registerUtility(renderer, ITemplateRenderer, name=spec) - finally: - lock.release() - else: - # spec is a package:relpath resource spec - renderer = reg.queryUtility(ITemplateRenderer, name=spec) - if renderer is None: - try: - package_name, filename = spec.split(':', 1) - except ValueError: # pragma: no cover - # somehow we were passed a relative pathname; this - # should die - package_name = caller_package(4).__name__ - filename = spec - abspath = pkg_resources.resource_filename(package_name, filename) - if not pkg_resources.resource_exists(package_name, filename): - raise ValueError( - 'Missing template resource: %s (%s)' % (spec, abspath)) - renderer = impl(abspath) - settings = info.settings - if settings and not settings.get('reload_resources'): +class ChameleonRendererLookup(object): + implements(IChameleonLookup) + def __init__(self, impl, registry): + self.impl = impl + self.registry = registry + self.lock = threading.Lock() + + def get_spec(self, name, package): + spec = name + isabs = os.path.isabs(name) + + if (not isabs) and (not ':' in name) and package: + # relative resource spec + if not isabs: + pp = package_path(package) + spec = os.path.join(pp, spec) + spec = resource_spec_from_abspath(spec, package) + return spec + + @reify # wait until completely necessary to look up translator + def translate(self): + return self.registry.queryUtility(IChameleonTranslate) + + @reify # wait until completely necessary to look up debug_templates + def debug(self): + settings = self.registry.settings or {} + return settings.get('debug_templates', False) + + @reify # wait until completely necessary to look up reload_templates + def auto_reload(self): + settings = self.registry.settings or {} + return settings.get('reload_templates', False) + + def __call__(self, info): + spec = self.get_spec(info.name, info.package) + registry = info.registry + + if os.path.isabs(spec): + # 'spec' is an absolute filename + if not os.path.exists(spec): + raise ValueError('Missing template file: %s' % spec) + renderer = registry.queryUtility(ITemplateRenderer, name=spec) + if renderer is None: + renderer = self.impl(spec, self) # cache the template try: - lock.acquire() - reg.registerUtility(renderer, ITemplateRenderer, name=spec) + self.lock.acquire() + registry.registerUtility(renderer, + ITemplateRenderer, name=spec) finally: - lock.release() - - return renderer + self.lock.release() + else: + # spec is a package:relpath resource spec + renderer = registry.queryUtility(ITemplateRenderer, name=spec) + if renderer is None: + try: + package_name, filename = spec.split(':', 1) + except ValueError: # pragma: no cover + # somehow we were passed a relative pathname; this + # should die + package_name = caller_package(4).__name__ + filename = spec + abspath = pkg_resources.resource_filename(package_name, + filename) + if not pkg_resources.resource_exists(package_name, filename): + raise ValueError( + 'Missing template resource: %s (%s)' % (spec, abspath)) + renderer = self.impl(abspath, self) + settings = info.settings or {} + if not settings.get('reload_resources'): + # cache the template + self.lock.acquire() + try: + registry.registerUtility(renderer, ITemplateRenderer, + name=spec) + finally: + self.lock.release() + + return renderer + +registry_lock = threading.Lock() -def renderer_from_name(path, package=None): # XXX deprecate? +def template_renderer_factory(info, impl, lock=registry_lock): + registry = info.registry + lookup = registry.queryUtility(IChameleonLookup, name=info.type) + if lookup is None: + lookup = ChameleonRendererLookup(impl, registry) + lock.acquire() + try: + registry.registerUtility(lookup, IChameleonLookup, name=info.type) + finally: + lock.release() + return lookup(info) + +# XXX deprecate +def renderer_from_name(path, package=None): return RendererHelper(name=path, package=package).renderer class RendererHelper(object): diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index 764124502..a42b92bfa 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -50,7 +50,8 @@ class TextTemplateRendererTests(Base, unittest.TestCase): from zope.interface.verify import verifyObject from pyramid.interfaces import ITemplateRenderer path = self._getTemplatePath('minimal.txt') - verifyObject(ITemplateRenderer, self._makeOne(path)) + lookup = DummyLookup() + verifyObject(ITemplateRenderer, self._makeOne(path, lookup)) def test_class_implements_ITemplate(self): from zope.interface.verify import verifyClass @@ -59,69 +60,73 @@ class TextTemplateRendererTests(Base, unittest.TestCase): def test_template_reified(self): minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template, instance.__dict__['template']) def test_template_with_ichameleon_translate(self): - from pyramid.interfaces import IChameleonTranslate - def ct(): pass - self.config.registry.registerUtility(ct, IChameleonTranslate) minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template - self.assertEqual(template.translate, ct) + self.assertEqual(template.translate, lookup.translate) def test_template_with_debug_templates(self): - self.config.add_settings({'debug_templates':True}) minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.debug = True + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.debug, True) def test_template_with_reload_templates(self): - self.config.add_settings({'reload_templates':True}) minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.auto_reload = True + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, True) - def test_template_with_emptydict(self): - from pyramid.interfaces import ISettings - self.config.registry.registerUtility({}, ISettings) + def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.auto_reload = False + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, False) - self.assertEqual(template.debug, False) def test_call(self): minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) result = instance({}, {}) self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) def test_call_nonminimal(self): nonminimal = self._getTemplatePath('nonminimal.txt') - instance = self._makeOne(nonminimal) + lookup = DummyLookup() + instance = self._makeOne(nonminimal, lookup) result = instance({'name':'Chris'}, {}) self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello, Chris!\n') def test_implementation(self): minimal = self._getTemplatePath('minimal.txt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) result = instance.implementation()() self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') @@ -196,3 +201,8 @@ class GetTemplateTests(Base, unittest.TestCase): result = self._callFUT('foo') self.failUnless(result is renderer.template) +class DummyLookup(object): + auto_reload=True + debug = True + def translate(self, msg): pass + diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index a969bac00..8f0b3f03c 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -43,7 +43,8 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): from zope.interface.verify import verifyObject from pyramid.interfaces import ITemplateRenderer path = self._getTemplatePath('minimal.pt') - verifyObject(ITemplateRenderer, self._makeOne(path)) + lookup = DummyLookup() + verifyObject(ITemplateRenderer, self._makeOne(path, lookup)) def test_class_implements_ITemplate(self): from zope.interface.verify import verifyClass @@ -52,7 +53,8 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): def test_call(self): minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) result = instance({}, {}) self.failUnless(isinstance(result, unicode)) self.assertEqual(result, @@ -60,55 +62,66 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): def test_template_reified(self): minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template, instance.__dict__['template']) def test_template_with_ichameleon_translate(self): - from pyramid.interfaces import IChameleonTranslate - def ct(): pass - self.config.registry.registerUtility(ct, IChameleonTranslate) minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template - self.assertEqual(template.translate, ct) + self.assertEqual(template.translate, lookup.translate) def test_template_with_debug_templates(self): - self.config.add_settings({'debug_templates':True}) minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.debug = True + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.debug, True) + def test_template_without_debug_templates(self): + minimal = self._getTemplatePath('minimal.pt') + lookup = DummyLookup() + lookup.debug = False + instance = self._makeOne(minimal, lookup) + self.failIf('template' in instance.__dict__) + template = instance.template + self.assertEqual(template.debug, False) + def test_template_with_reload_templates(self): - self.config.add_settings({'reload_templates':True}) minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.auto_reload = True + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, True) - def test_template_with_emptydict(self): - from pyramid.interfaces import ISettings - self.config.registry.registerUtility({}, ISettings) + def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + lookup.auto_reload = False + instance = self._makeOne(minimal, lookup) self.failIf('template' in instance.__dict__) template = instance.template self.assertEqual(template.auto_reload, False) - self.assertEqual(template.debug, False) def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) def test_implementation(self): minimal = self._getTemplatePath('minimal.pt') - instance = self._makeOne(minimal) + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) result = instance.implementation()() self.failUnless(isinstance(result, unicode)) self.assertEqual(result, @@ -192,3 +205,8 @@ class GetTemplateTests(Base, unittest.TestCase): self._registerUtility(rf, IRendererFactory, name='foo') result = self._callFUT('foo') self.failUnless(result is renderer.template) + +class DummyLookup(object): + auto_reload=True + debug = True + def translate(self, msg): pass diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index b4a6892db..1dab39a6e 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -23,6 +23,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) self.assertRaises(ValueError, self._callFUT, info, None) @@ -37,6 +38,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) result = self._callFUT(info, None) self.failUnless(result is renderer) @@ -52,6 +54,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) result = self._callFUT(info, None) self.failUnless(result is renderer) @@ -66,6 +69,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) result = self._callFUT(info, None) self.failUnless(renderer is result) @@ -82,6 +86,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':pyramid.tests, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) result = self._callFUT(info, None) self.failUnless(renderer is result) @@ -93,6 +98,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) self.assertRaises(ValueError, self._callFUT, info, None) @@ -107,6 +113,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) renderer = {} testing.registerUtility(renderer, ITemplateRenderer, name=spec) @@ -126,6 +133,7 @@ class TestTemplateRendererFactory(unittest.TestCase): 'package':None, 'registry':self.config.registry, 'settings':{}, + 'type':'type', }) result = self._callFUT(info, factory) self.failUnless(result is renderer) @@ -137,7 +145,6 @@ class TestTemplateRendererFactory(unittest.TestCase): def test_reload_resources_true(self): import pyramid.tests - from pyramid.threadlocal import get_current_registry from pyramid.interfaces import ISettings from pyramid.interfaces import ITemplateRenderer settings = {'reload_resources':True} @@ -145,43 +152,44 @@ class TestTemplateRendererFactory(unittest.TestCase): renderer = {} factory = DummyFactory(renderer) spec = 'test_renderers.py' + reg = self.config.registry info = DummyRendererInfo({ 'name':spec, 'package':pyramid.tests, - 'registry':self.config.registry, - 'settings':{}, + 'registry':reg, + 'settings':settings, + 'type':'type', }) result = self._callFUT(info, factory) self.failUnless(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') - reg = get_current_registry() self.assertEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) def test_reload_resources_false(self): import pyramid.tests - from pyramid.threadlocal import get_current_registry from pyramid.interfaces import ITemplateRenderer settings = {'reload_resources':False} renderer = {} factory = DummyFactory(renderer) spec = 'test_renderers.py' + reg = self.config.registry info = DummyRendererInfo({ 'name':spec, 'package':pyramid.tests, - 'registry':self.config.registry, + 'registry':reg, 'settings':settings, + 'type':'type', }) result = self._callFUT(info, factory) self.failUnless(result is renderer) spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py') - reg = get_current_registry() self.assertNotEqual(reg.queryUtility(ITemplateRenderer, name=spec), None) class TestRendererFromName(unittest.TestCase): def setUp(self): - cleanUp() + self.config = cleanUp() def tearDown(self): cleanUp() @@ -191,8 +199,7 @@ class TestRendererFromName(unittest.TestCase): return renderer_from_name(path, package) def test_it(self): - from pyramid.threadlocal import get_current_registry - registry = get_current_registry() + registry = self.config.registry settings = {} registry.settings = settings from pyramid.interfaces import IRendererFactory @@ -211,8 +218,7 @@ class TestRendererFromName(unittest.TestCase): def test_it_with_package(self): import pyramid - from pyramid.threadlocal import get_current_registry - registry = get_current_registry() + registry = self.config.registry settings = {} registry.settings = settings from pyramid.interfaces import IRendererFactory @@ -534,7 +540,7 @@ class DummyFactory: def __init__(self, renderer): self.renderer = renderer - def __call__(self, path, **kw): + def __call__(self, path, lookup, **kw): self.path = path self.kw = kw return self.renderer -- cgit v1.2.3 From 2e2ed9467793500d5693e7061bacc550de5900a0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 04:09:13 -0500 Subject: gardening --- TODO.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO.txt b/TODO.txt index 70d760f45..66fc6c0ff 100644 --- a/TODO.txt +++ b/TODO.txt @@ -110,10 +110,6 @@ security.principals_allowed_by_permission - chameleon_text.TextTemplateRenderer.template (also uses get_settings) - - chameleon_zpt.ZPTTemplateRenderer.template (also uses get_settings) - resource.OverrideProvider._get_overrides: can't credibly be removed, because it stores an overrideprovider as a module-scope global. -- cgit v1.2.3 From 43a52989c77bbe07b9906bd6f971034b49b1c12c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 06:09:08 -0500 Subject: whitespace --- TODO.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index 66fc6c0ff..09cba3bb2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -130,8 +130,3 @@ - zcml.file_configure - Add static_url as method of request. - - - - - -- cgit v1.2.3 From b1e3633b3874d565c7b6debcf5051bf4e638d4f4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 06:09:52 -0500 Subject: less aggressive caching of settings n case someone uses a renderer at module scope --- pyramid/renderers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 94b58cf92..ba29f80d0 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -171,16 +171,16 @@ class ChameleonRendererLookup(object): spec = resource_spec_from_abspath(spec, package) return spec - @reify # wait until completely necessary to look up translator + @property # wait until completely necessary to look up translator def translate(self): return self.registry.queryUtility(IChameleonTranslate) - @reify # wait until completely necessary to look up debug_templates + @property # wait until completely necessary to look up debug_templates def debug(self): settings = self.registry.settings or {} return settings.get('debug_templates', False) - @reify # wait until completely necessary to look up reload_templates + @property # wait until completely necessary to look up reload_templates def auto_reload(self): settings = self.registry.settings or {} return settings.get('reload_templates', False) -- cgit v1.2.3 From 9e2e1cfa5d7b00b952b79a628347a89d2263f777 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 22:52:46 -0500 Subject: add convenience static_url method to request --- CHANGES.txt | 6 +++--- TODO.txt | 1 - pyramid/request.py | 35 ++++++++++++++++++++++++++++++++++- pyramid/tests/test_request.py | 26 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b9f6a69c7..f1e55ffce 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,9 +13,9 @@ Features - New API method: ``pyramid.settings.asbool``. -- New API methods for ``pyramid.request.Request``: ``model_url`` and - ``route_url``. These are simple passthroughs for their respective - functions in ``pyramid.url``. +- New API methods for ``pyramid.request.Request``: ``model_url``, + ``route_url``, and ``static_url``. These are simple passthroughs for their + respective functions in ``pyramid.url``. - The ``settings`` object which used to be available only when ``request.settings.get_settings`` was called is now available as diff --git a/TODO.txt b/TODO.txt index 09cba3bb2..afe9e8532 100644 --- a/TODO.txt +++ b/TODO.txt @@ -129,4 +129,3 @@ - zcml.file_configure -- Add static_url as method of request. diff --git a/pyramid/request.py b/pyramid/request.py index 891c33fff..dff70d93c 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -8,8 +8,9 @@ from pyramid.interfaces import ISessionFactory from pyramid.exceptions import ConfigurationError from pyramid.decorator import reify -from pyramid.url import route_url from pyramid.url import model_url +from pyramid.url import route_url +from pyramid.url import static_url class TemplateContext(object): pass @@ -220,6 +221,38 @@ class Request(WebobRequest): """ return model_url(model, self, *elements, **kw) + def static_url(self, path, **kw): + """ Generates a fully qualified URL for a static :term:`resource`. + The resource must live within a location defined via the + :meth:`pyramid.configuration.Configurator.add_static_view` + :term:`configuration declaration` or the ```` ZCML + directive (see :ref:`static_resources_section`). + + This is a convenience method. The result of calling + :meth:`pyramid.request.Request.static_url` is the same as calling + :func:`pyramid.url.static_url` with an explicit ``request`` parameter. + + The :meth:`pyramid.request.Request.static_url` method calls the + :func:`pyramid.url.static_url` function using the Request object as + the ``request`` argument. The ``*kw`` arguments passed to + :meth:`pyramid.request.Request.static_url` are passed through to + :func:`pyramid.url.static_url` unchanged and its result is returned. + + This call to :meth:`pyramid.request.Request.static_url`:: + + request.static_url('mypackage:static/foo.css') + + Is completely equivalent to calling :func:`pyramid.url.static_url` + like this:: + + from pyramid.url import static_url + static_url('mypackage:static/foo.css, request) + + See :func:`pyramid.url.static_url` for more information + + """ + return static_url(path, self, **kw) + # override default WebOb "environ['adhoc_attr']" mutation behavior __getattr__ = object.__getattribute__ __setattr__ = object.__setattr__ diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index d12b47642..a398bf3af 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -266,6 +266,24 @@ class TestRequest(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') + def test_static_url(self): + from pyramid.interfaces import IStaticURLInfo + environ = { + 'PATH_INFO':'/', + 'SERVER_NAME':'example.com', + 'SERVER_PORT':'5432', + 'QUERY_STRING':'', + 'wsgi.url_scheme':'http', + } + request = self._makeOne(environ) + info = DummyStaticURLInfo('abc') + self.config.registry.registerUtility(info, IStaticURLInfo) + result = request.static_url('pyramid.tests:static/foo.css') + self.assertEqual(result, 'abc') + self.assertEqual(info.args, + ('pyramid.tests:static/foo.css', request, {}) ) + + class Test_route_request_iface(unittest.TestCase): def _callFUT(self, name): from pyramid.request import route_request_iface @@ -323,3 +341,11 @@ class DummyRoute: def generate(self, kw): self.kw = kw return self.result + +class DummyStaticURLInfo: + def __init__(self, result): + self.result = result + + def generate(self, path, request, **kw): + self.args = path, request, kw + return self.result -- cgit v1.2.3 From 36c1596c229bbaf7745c2a1f2c4dfcfdefb5535d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 15 Nov 2010 22:58:14 -0500 Subject: docs backrefs --- pyramid/url.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/url.py b/pyramid/url.py index b740aaca7..fa15e6364 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -18,6 +18,9 @@ def route_url(route_name, request, *elements, **kw): """Generates a fully qualified URL for a named :app:`Pyramid` :term:`route configuration`. + .. note:: Calling :meth:`pyramid.Request.route_url` can be used to + achieve the same result as :func:`pyramid.url.route_url`. + Use the route's ``name`` as the first positional argument. Use a request object as the second positional argument. Additional positional arguments are appended to the URL as path segments @@ -102,6 +105,7 @@ def route_url(route_name, request, *elements, **kw): If the route object which matches the ``route_name`` argument has a :term:`pregenerator`, the ``*elements`` and ``**kw`` arguments arguments passed to this function might be augmented or changed. + """ try: reg = request.registry @@ -157,6 +161,9 @@ def model_url(model, request, *elements, **kw): overall result of this function is always a UTF-8 encoded string (never Unicode). + .. note:: Calling :meth:`pyramid.Request.model_url` can be used to + achieve the same result as :func:`pyramid.url.model_url`. + Examples:: model_url(context, request) => @@ -270,6 +277,9 @@ def static_url(path, request, **kw): :term:`configuration declaration` or the ```` ZCML directive (see :ref:`static_resources_section`). + .. note:: Calling :meth:`pyramid.Request.static_url` can be used to + achieve the same result as :func:`pyramid.url.static_url`. + Example:: static_url('mypackage:static/foo.css', request) => -- cgit v1.2.3 From ae60bafb11bcf3a85a8ed3c78b8fc57961e8c3b6 Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Tue, 16 Nov 2010 10:46:23 -0800 Subject: - Added Mako TemplateLookup settings for ``mako.error_handler``, ``mako.default_filters``, and ``mako.imports``. --- CHANGES.txt | 3 +++ docs/narr/environment.rst | 48 ++++++++++++++++++++++++++++++++++++++++++++++ pyramid/mako_templating.py | 6 ++++++ 3 files changed, 57 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index f1e55ffce..e8595d6fc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,9 @@ Next release Features -------- +- Added Mako TemplateLookup settings for ``mako.error_handler``, + ``mako.default_filters``, and ``mako.imports``. + - Normalized all paster templates: each now uses the name ``main`` to represent the function that returns a WSGI application, each now uses WebError, each now has roughly the same shape of development.ini style. diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 2aa4064cd..6df0998d6 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -201,6 +201,54 @@ should be changed accordingly. | | +-----------------------------+ +Mako Error Handler +++++++++++++++++++ + +Python callable which is called whenever Mako compile or runtime exceptions +occur. The callable is passed the current context as well as the exception. If +the callable returns True, the exception is considered to be handled, else it +is re-raised after the function completes. Is used to provide custom +error-rendering functions. + ++-----------------------------+ +| Config File Setting Name | ++=============================+ +| ``mako.error_handler`` _ | +| | +| | +| | ++-----------------------------+ + +Mako Default Filters +++++++++++++++++++++ + +List of string filter names that will be applied to all Mako expressions. + ++-----------------------------+ +| Config File Setting Name | ++=============================+ +| ``mako.default_filters`` _ | +| | +| | +| | ++-----------------------------+ + +Mako Import ++++++++++++ + +String list of Python statements, typically individual “import” lines, which +will be placed into the module level preamble of all generated Python modules. + + ++-----------------------------+ +| Config File Setting Name | ++=============================+ +| ``mako.imports``________ _ | +| | +| | +| | ++-----------------------------+ + Examples -------- diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index b63009e2c..e2330f3ad 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -64,6 +64,9 @@ def renderer_factory(info): directories = settings.get('mako.directories') module_directory = settings.get('mako.module_directory') input_encoding = settings.get('mako.input_encoding', 'utf-8') + error_handler = settings.get('mako.error_handler', None) + default_filters = settings.get('mako.default_filters', []) + imports = settings.get('mako.imports', []) if directories is None: raise ConfigurationError( 'Mako template used without a ``mako.directories`` setting') @@ -72,6 +75,9 @@ def renderer_factory(info): lookup = PkgResourceTemplateLookup(directories=directories, module_directory=module_directory, input_encoding=input_encoding, + error_handler=error_handler, + default_filters=default_filters, + imports=imports, filesystem_checks=reload_templates) registry_lock.acquire() try: -- cgit v1.2.3 From 3dbf5edd33172b7ee70783dfde77949c2a3c3641 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 15:40:39 -0500 Subject: fix rendering --- docs/narr/environment.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 6df0998d6..ecf85e464 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -213,7 +213,7 @@ error-rendering functions. +-----------------------------+ | Config File Setting Name | +=============================+ -| ``mako.error_handler`` _ | +| ``mako.error_handler`` | | | | | | | @@ -227,7 +227,7 @@ List of string filter names that will be applied to all Mako expressions. +-----------------------------+ | Config File Setting Name | +=============================+ -| ``mako.default_filters`` _ | +| ``mako.default_filters`` | | | | | | | @@ -243,7 +243,7 @@ will be placed into the module level preamble of all generated Python modules. +-----------------------------+ | Config File Setting Name | +=============================+ -| ``mako.imports``________ _ | +| ``mako.imports`` | | | | | | | -- cgit v1.2.3 From 3fa99493e9ba3d1dc05d8e917ee2f1bc1b6ed14e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 15:51:22 -0500 Subject: prep for 1.0a3 --- CHANGES.txt | 4 ++-- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e8595d6fc..942720112 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.0a3 (2010-11-16) +================== Features -------- diff --git a/docs/conf.py b/docs/conf.py index c2ecb1e8d..b4448b803 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -73,7 +73,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.0a2' +version = '1.0a3' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index b220d2914..6dddc7643 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ############################################################################## -__version__ = '0.0' +__version__ = '1.0a3' import os import platform -- cgit v1.2.3 From 745a613c164c3129113508afbf7ea5dedcdbcadb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 15:58:04 -0500 Subject: back to development --- CHANGES.txt | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 942720112..7dee3f99d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +Next release +============ + + 1.0a3 (2010-11-16) ================== diff --git a/setup.py b/setup.py index 6dddc7643..b220d2914 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ############################################################################## -__version__ = '1.0a3' +__version__ = '0.0' import os import platform -- cgit v1.2.3 From 111a6fbae020f99c206b97459920908baad604cd Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 18:17:54 -0500 Subject: - Add deprecation warnings to import of ``pyramid.chameleon_text`` and ``pyramid.chameleon_zpt`` of ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response``. --- CHANGES.txt | 7 +++++++ TODO.txt | 3 --- pyramid/chameleon_text.py | 23 +++++++++++++++++++++++ pyramid/chameleon_zpt.py | 22 ++++++++++++++++++++++ pyramid/tests/test_chameleon_text.py | 4 ++++ pyramid/tests/test_chameleon_zpt.py | 4 ++++ 6 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7dee3f99d..f25d0e83f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Next release ============ +Bug Fixes +--------- + +- Add deprecation warnings to import of ``pyramid.chameleon_text`` and + ``pyramid.chameleon_zpt`` of ``get_renderer``, ``get_template``, + ``render_template``, and ``render_template_to_response``. + 1.0a3 (2010-11-16) ================== diff --git a/TODO.txt b/TODO.txt index afe9e8532..33d39a825 100644 --- a/TODO.txt +++ b/TODO.txt @@ -120,9 +120,6 @@ - Add deprecation warnings for: - - Use of chameleon_zpt and chameleon_text templating functions (use - renderer API instead). - - settings.get_settings - zcml.zcml_configure diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py index 1f31f3add..f8d96ddf5 100644 --- a/pyramid/chameleon_text.py +++ b/pyramid/chameleon_text.py @@ -1,5 +1,6 @@ import sys +from zope.deprecation import deprecated from zope.interface import implements try: @@ -80,6 +81,11 @@ def get_renderer(path): factory = renderers.RendererHelper(path, package=package) return factory.get_renderer() +deprecated( + 'get_renderer', + '(pyramid.chameleon_text.get_renderer is deprecated ' + 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)') + def get_template(path): """ Return the underyling object representing a :term:`Chameleon` text template using the template implied by the ``path`` argument. @@ -94,6 +100,12 @@ def get_template(path): factory = renderers.RendererHelper(path, package=package) return factory.get_renderer().implementation() +deprecated( + 'render_template', + '(pyramid.chameleon_text.render_template is deprecated ' + 'as of Pyramid 1.0; instead use ' + 'pyramid.renderers.get_renderer().implementation())') + def render_template(path, **kw): """ Render a :term:`Chameleon` text template using the template implied by the ``path`` argument. The ``path`` argument may be a @@ -110,6 +122,11 @@ def render_template(path, **kw): renderer = renderers.RendererHelper(path, package=package) return renderer.render(kw, None, request=request) +deprecated( + 'render_template', + '(pyramid.chameleon_text.render_template is deprecated ' + 'as of Pyramid 1.0; instead use pyramid.renderers.render)') + def render_template_to_response(path, **kw): """ Render a :term:`Chameleon` text template using the template implied by the ``path`` argument. The ``path`` argument may be a @@ -126,3 +143,9 @@ def render_template_to_response(path, **kw): request = kw.pop('request', None) renderer = renderers.RendererHelper(path, package=package) return renderer.render_to_response(kw, None, request=request) + +deprecated( + 'render_template_to_response', + '(pyramid.chameleon_text.render_template_to_response is deprecated ' + 'as of Pyramid 1.0; instead use pyramid.renderers.render_to_response)') + diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index 6aae59a87..55bef1d0c 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -1,6 +1,7 @@ import sys import threading +from zope.deprecation import deprecated from zope.interface import implements try: @@ -65,6 +66,11 @@ def get_renderer(path): factory = renderers.RendererHelper(name=path, package=package) return factory.get_renderer() +deprecated( + 'get_renderer', + '(pyramid.chameleon_zpt.get_renderer is deprecated ' + 'as of Pyramid 1.0; instead use pyramid.renderers.get_renderer)') + def get_template(path): """ Return the underyling object representing a :term:`Chameleon` ZPT template using the template implied by the ``path`` argument. @@ -79,6 +85,12 @@ def get_template(path): factory = renderers.RendererHelper(name=path, package=package) return factory.get_renderer().implementation() +deprecated( + 'get_template', + '(pyramid.chameleon_zpt.get_template is deprecated ' + 'as of Pyramid 1.0; instead use ' + 'pyramid.renderers.get_renderer().implementation())') + def render_template(path, **kw): """ Render a :term:`Chameleon` ZPT template using the template implied by the ``path`` argument. The ``path`` argument may be a @@ -95,6 +107,11 @@ def render_template(path, **kw): renderer = renderers.RendererHelper(name=path, package=package) return renderer.render(kw, None, request=request) +deprecated( + 'render_template', + '(pyramid.chameleon_zpt.render_template is deprecated as of Pyramid 1.0; ' + 'instead use pyramid.renderers.render)') + def render_template_to_response(path, **kw): """ Render a :term:`Chameleon` ZPT template using the template implied by the ``path`` argument. The ``path`` argument may be a @@ -111,3 +128,8 @@ def render_template_to_response(path, **kw): request = kw.pop('request', None) renderer = renderers.RendererHelper(name=path, package=package) return renderer.render_to_response(kw, None, request=request) + +deprecated( + 'render_template', + '(pyramid.chameleon_zpt.render_template_to_response is deprecated; as of ' + 'Pyramid 1.0, instead use pyramid.renderers.render_to_response)') diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index a42b92bfa..027fa9474 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -11,9 +11,13 @@ class Base: os.unlink(self._getTemplatePath('minimal.txt.py')) except: pass + from zope.deprecation import __show__ + __show__.off() def tearDown(self): cleanUp() + from zope.deprecation import __show__ + __show__.on() def _getTemplatePath(self, name): import os diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 8f0b3f03c..3e508ce6a 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -5,9 +5,13 @@ from pyramid.testing import cleanUp class Base(object): def setUp(self): cleanUp() + from zope.deprecation import __show__ + __show__.off() def tearDown(self): cleanUp() + from zope.deprecation import __show__ + __show__.on() def _getTemplatePath(self, name): import os -- cgit v1.2.3 From ff3bc6597257903a4a05cc95fc9276f4fa120eb8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 20:14:37 -0500 Subject: get deprecations right --- pyramid/chameleon_text.py | 4 ++-- pyramid/chameleon_zpt.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py index f8d96ddf5..6eb7af4d0 100644 --- a/pyramid/chameleon_text.py +++ b/pyramid/chameleon_text.py @@ -101,8 +101,8 @@ def get_template(path): return factory.get_renderer().implementation() deprecated( - 'render_template', - '(pyramid.chameleon_text.render_template is deprecated ' + 'get_template', + '(pyramid.chameleon_text.get_template is deprecated ' 'as of Pyramid 1.0; instead use ' 'pyramid.renderers.get_renderer().implementation())') diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index 55bef1d0c..5c2d6f70f 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -130,6 +130,6 @@ def render_template_to_response(path, **kw): return renderer.render_to_response(kw, None, request=request) deprecated( - 'render_template', + 'render_template_to_response', '(pyramid.chameleon_zpt.render_template_to_response is deprecated; as of ' 'Pyramid 1.0, instead use pyramid.renderers.render_to_response)') -- cgit v1.2.3 From f4c3876685fa222eed96985bbdf37347ad362df2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 20:28:59 -0500 Subject: prevent deprecation warning during tests --- pyramid/tests/test_chameleon_zpt.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 3e508ce6a..056b95937 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -145,12 +145,6 @@ class RenderTemplateTests(Base, unittest.TestCase): '
\n
') class RenderTemplateToResponseTests(Base, unittest.TestCase): - def setUp(self): - cleanUp() - - def tearDown(self): - cleanUp() - def _callFUT(self, name, **kw): from pyramid.chameleon_zpt import render_template_to_response return render_template_to_response(name, **kw) -- cgit v1.2.3 From b68aadc3867a95a6376bb2157cc93210eb62a104 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 20:31:04 -0500 Subject: - The ``pyramid.settings.get_settings`` API is now deprecated. Use ``pyramid.threadlocals.get_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). --- CHANGES.txt | 7 +++++++ docs/narr/i18n.rst | 9 +++++---- docs/narr/static.rst | 31 +++++++++++++++---------------- pyramid/configuration.py | 23 ++++++++++------------- pyramid/settings.py | 13 +++++++++++++ pyramid/testing.py | 8 ++++---- pyramid/tests/test_settings.py | 4 ++++ 7 files changed, 58 insertions(+), 37 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f25d0e83f..d132b4e71 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,13 @@ Bug Fixes ``pyramid.chameleon_zpt`` of ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response``. +Deprecations +------------ + +- The ``pyramid.settings.get_settings`` API is now deprecated. Use + ``pyramid.threadlocals.get_registry().settings`` instead or use the + ``settings`` attribute of the registry available from the request + (``request.registry.settings``). 1.0a3 (2010-11-16) ================== diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 703883fb2..9e2071872 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -773,8 +773,8 @@ If this setting is supplied within the :app:`Pyramid` application .. code-block:: python :linenos: - from pyramid.setttings import get_settings - settings = get_settings() + from pyramid.threadlocal import get_current_registry + settings = get_current_registry().settings default_locale_name = settings['default_locale_name'] "Detecting" Available Languages @@ -822,8 +822,9 @@ Then as a part of the code of a custom :term:`locale negotiator`: .. code-block:: py - from pyramid.settings import get_settings - languages = get_settings()['available_languages'].split() + from pyramid.threadlocal import get_current_registry + settings = get_current_registry().settings + languages = settings['available_languages'].split() This is only a suggestion. You can create your own "available languages" configuration scheme as necessary. diff --git a/docs/narr/static.rst b/docs/narr/static.rst index efeabd012..a01cbbabf 100644 --- a/docs/narr/static.rst +++ b/docs/narr/static.rst @@ -69,22 +69,21 @@ when generating a URL using :func:`pyramid.url.static_url`. .. note:: Using :func:`pyramid.url.static_url` in conjunction with a - :meth:`pyramid.configuration.Configurator.add_static_view` makes - it possible to put static media on a separate webserver during - production (if the ``name`` argument to - :meth:`pyramid.configuration.Configurator.add_static_view` is a - URL), while keeping static media package-internal and served by the - development webserver during development (if the ``name`` argument - to :meth:`pyramid.configuration.Configurator.add_static_view` is - a view name). To create such a circumstance, we suggest using the - :func:`pyramid.settings.get_settings` API in conjunction with a - setting in the application ``.ini`` file named ``media_location``. - Then set the value of ``media_location`` to either a view name or a - URL depending on whether the application is being run in - development or in production (use a different `.ini`` file for - production than you do for development). This is just a suggestion - for a pattern; any setting name other than ``media_location`` could - be used. + :meth:`pyramid.configuration.Configurator.add_static_view` makes it + possible to put static media on a separate webserver during production (if + the ``name`` argument to + :meth:`pyramid.configuration.Configurator.add_static_view` is a URL), + while keeping static media package-internal and served by the development + webserver during development (if the ``name`` argument to + :meth:`pyramid.configuration.Configurator.add_static_view` is a view + name). To create such a circumstance, we suggest using the + :attr:`pyramid.registry.Registry.settings` API in conjunction with a + setting in the application ``.ini`` file named ``media_location``. Then + set the value of ``media_location`` to either a view name or a URL + depending on whether the application is being run in development or in + production (use a different `.ini`` file for production than you do for + development). This is just a suggestion for a pattern; any setting name + other than ``media_location`` could be used. For example, :meth:`pyramid.configuration.Configurator.add_static_view` may be fed a ``name`` argument which is ``http://example.com/images``: diff --git a/pyramid/configuration.py b/pyramid/configuration.py index cee65a982..3d1530406 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -570,11 +570,10 @@ class Configurator(object): return subscriber def add_settings(self, settings=None, **kw): - """Augment the ``settings`` argument passed in to the - Configurator constructor with one or more 'setting' key/value - pairs. A setting is a single key/value pair in the - dictionary-ish object returned from the API - :func:`pyramid.settings.get_settings` and + """Augment the ``settings`` argument passed in to the Configurator + constructor with one or more 'setting' key/value pairs. A setting is + a single key/value pair in the dictionary-ish object returned from + the API :attr:`pyramid.registry.Registry.settings` and :meth:`pyramid.configuration.Configurator.get_settings`. You may pass a dictionary:: @@ -585,11 +584,10 @@ class Configurator(object): config.add_settings(external_uri='http://example.com') - This function is useful when you need to test code that calls the - :func:`pyramid.settings.get_settings` API (or the - :meth:`pyramid.configuration.Configurator.get_settings` API or - accesses ``request.settings``) and which uses return values from that - API. + This function is useful when you need to test code that accesses the + :attr:`pyramid.registry.Registry.settings` API (or the + :meth:`pyramid.configuration.Configurator.get_settings` API) and + which uses values from that API. """ if settings is None: settings = {} @@ -610,9 +608,8 @@ class Configurator(object): .. note:: For backwards compatibility, dictionary keys can also be looked up as attributes of the settings object. - .. note:: the :class:`pyramid.settings.get_settings` and function - performs the same duty and the settings attribute can also be - accessed as :attr:`pyramid.registry.Registry.settings` + .. note:: the :attr:`pyramid.registry.Registry.settings` API + performs the same duty. """ return self.registry.settings diff --git a/pyramid/settings.py b/pyramid/settings.py index 96ad3336a..d6b0a6470 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -1,5 +1,6 @@ import os +from zope.deprecation import deprecated from zope.interface import implements from pyramid.interfaces import ISettings @@ -75,10 +76,22 @@ def get_settings(): .. note:: the :class:`pyramid.configuration.Configurator.get_settings` method performs the same duty. + + .. warning:: This method is deprecated as of Pyramid 1.0. Use + ``pyramid.threadlocals.get_registry().settings`` instead or use the + ``settings`` attribute of the registry available from the request + (``request.registry.settings``). """ reg = get_current_registry() return reg.settings +deprecated( + 'get_settings', + '(pyramid.settings.get_settings is deprecated as of Pyramid 1.0. Use' + '``pyramid.threadlocals.get_registry().settings`` instead or use the ' + '``settings`` attribute of the registry available from the request ' + '(``request.registry.settings``)).') + def asbool(s): """ Return the boolean value ``True`` if the case-lowered value of string input ``s`` is any of ``t``, ``true``, ``y``, ``on``, or ``1``, otherwise diff --git a/pyramid/testing.py b/pyramid/testing.py index e8e843bff..84ab77935 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -277,7 +277,7 @@ def registerRoute(pattern, name, factory=None): def registerSettings(dictarg=None, **kw): """Register one or more 'setting' key/value pairs. A setting is a single key/value pair in the dictionary-ish object returned from - the API :func:`pyramid.settings.get_settings`. + the API :attr:`pyramid.registry.Registry.settings`. You may pass a dictionary:: @@ -287,9 +287,9 @@ def registerSettings(dictarg=None, **kw): registerSettings(external_uri='http://example.com') - Use of this function is required when you need to test code that - calls the :func:`pyramid.settings.get_settings` API and which - uses return values from that API. + Use of this function is required when you need to test code that calls + the :attr:`pyramid.registry.Registry.settings` API and which uses return + values from that API. .. warning:: This API is deprecated as of :app:`Pyramid` 1.0. Instead use the diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py index 12b1174de..49c1e928f 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -184,9 +184,13 @@ class TestGetSettings(unittest.TestCase): registry = Registry('testing') self.config = Configurator(registry=registry) self.config.begin() + from zope.deprecation import __show__ + __show__.off() def tearDown(self): self.config.end() + from zope.deprecation import __show__ + __show__.on() def _callFUT(self): from pyramid.settings import get_settings -- cgit v1.2.3 From 34f44d844ffe75738046a154202a6faf4d5dfc38 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 20:50:28 -0500 Subject: - Add deprecation warning for import of ``pyramid.zcml.zcml_configure`` and ``pyramid.zcml.file_configure``. - The ``pyramid.testing.zcml_configure`` API has been removed. It had been advertised as removed since 1.2a1, but hadn't actually been. --- CHANGES.txt | 9 +++++++++ TODO.txt | 7 ------- pyramid/testing.py | 3 --- pyramid/tests/test_zcml.py | 4 ++++ pyramid/zcml.py | 12 ++++++++++++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d132b4e71..28637c1a7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,15 @@ Bug Fixes ``pyramid.chameleon_zpt`` of ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response``. +- Add deprecation warning for import of ``pyramid.zcml.zcml_configure`` and + ``pyramid.zcml.file_configure``. + +Backwards Incompatibilities +--------------------------- + +- The ``pyramid.testing.zcml_configure`` API has been removed. It had been + advertised as removed since 1.2a1, but hadn't actually been. + Deprecations ------------ diff --git a/TODO.txt b/TODO.txt index 33d39a825..759b9bb70 100644 --- a/TODO.txt +++ b/TODO.txt @@ -118,11 +118,4 @@ Configurator.add_translation_dirs: not passed any context but a message, can't credibly be removed. -- Add deprecation warnings for: - - - settings.get_settings - - - zcml.zcml_configure - - - zcml.file_configure diff --git a/pyramid/testing.py b/pyramid/testing.py index 84ab77935..4bc4728c0 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -20,9 +20,6 @@ from pyramid.security import Everyone from pyramid.security import has_permission from pyramid.threadlocal import get_current_registry from pyramid.threadlocal import manager -from pyramid.zcml import zcml_configure # API - -zcml_configure # prevent pyflakes from complaining _marker = object() diff --git a/pyramid/tests/test_zcml.py b/pyramid/tests/test_zcml.py index f4a6e81b6..905a53287 100644 --- a/pyramid/tests/test_zcml.py +++ b/pyramid/tests/test_zcml.py @@ -803,6 +803,8 @@ class TestZCMLConfigure(unittest.TestCase): return zcml_configure(path, package) def setUp(self): + from zope.deprecation import __show__ + __show__.off() testing.setUp() self.tempdir = None import sys @@ -822,6 +824,8 @@ class TestZCMLConfigure(unittest.TestCase): self.tempdir = tempdir def tearDown(self): + from zope.deprecation import __show__ + __show__.on() testing.tearDown() import sys import shutil diff --git a/pyramid/zcml.py b/pyramid/zcml.py index a2fdec314..3104ebac0 100644 --- a/pyramid/zcml.py +++ b/pyramid/zcml.py @@ -6,6 +6,8 @@ from zope.configuration.fields import GlobalInterface from zope.configuration.fields import GlobalObject from zope.configuration.fields import Tokens +from zope.deprecation import deprecated + from zope.interface import Interface from zope.interface import implementedBy from zope.interface import providedBy @@ -937,6 +939,16 @@ def zcml_configure(name, package): file_configure = zcml_configure # backwards compat (>0.8.1) +deprecated( + 'zcml_configure', + '(pyramid.zcml.zcml_configure is deprecated as of Pyramid 1.0. Use' + '``pyramid.configuration.Configurator.load_zcml`` instead.) ') + +deprecated( + 'file_configure', + '(pyramid.zcml.file_configure is deprecated as of Pyramid 1.0. Use' + '``pyramid.configuration.Configurator.load_zcml`` instead.) ') + def _rolledUpFactory(factories): def factory(ob): for f in factories: -- cgit v1.2.3 From 84df816470b6745c628c177c99dee9fa844812c4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 20:51:56 -0500 Subject: point at correct location of get_current_registry --- CHANGES.txt | 2 +- pyramid/settings.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 28637c1a7..2737e6893 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -21,7 +21,7 @@ Deprecations ------------ - The ``pyramid.settings.get_settings`` API is now deprecated. Use - ``pyramid.threadlocals.get_registry().settings`` instead or use the + ``pyramid.threadlocals.get_current_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). diff --git a/pyramid/settings.py b/pyramid/settings.py index d6b0a6470..64b108421 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -78,8 +78,8 @@ def get_settings(): performs the same duty. .. warning:: This method is deprecated as of Pyramid 1.0. Use - ``pyramid.threadlocals.get_registry().settings`` instead or use the - ``settings`` attribute of the registry available from the request + ``pyramid.threadlocals.get_current_registry().settings`` instead or use ' + the ``settings`` attribute of the registry available from the request (``request.registry.settings``). """ reg = get_current_registry() @@ -88,8 +88,8 @@ def get_settings(): deprecated( 'get_settings', '(pyramid.settings.get_settings is deprecated as of Pyramid 1.0. Use' - '``pyramid.threadlocals.get_registry().settings`` instead or use the ' - '``settings`` attribute of the registry available from the request ' + '``pyramid.threadlocals.get_current_registry().settings`` instead or use ' + 'the ``settings`` attribute of the registry available from the request ' '(``request.registry.settings``)).') def asbool(s): -- cgit v1.2.3 From 327b51fe1f1160b98289070c181b088addc16dd0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 21:48:25 -0500 Subject: add ignores for jython usual suspects --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 09439306e..5d46de01f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ *.egg *.egg-info *.pyc +*$py.class *.pt.py *.txt.py .coverage env26 env24 env27 +jyenv build/ dist/ -- cgit v1.2.3 From adc1670a54d270f2a17eff6898cc0931eabea450 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 21:49:44 -0500 Subject: make some tests which previously failed on jython pass --- pyramid/tests/test_integration.py | 15 ++++++++++----- pyramid/tests/viewdecoratorapp/views/templates/foo.mak | 3 +++ pyramid/tests/viewdecoratorapp/views/templates/foo.pt | 3 --- pyramid/tests/viewdecoratorapp/views/views.py | 10 ++-------- 4 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 pyramid/tests/viewdecoratorapp/views/templates/foo.mak delete mode 100644 pyramid/tests/viewdecoratorapp/views/templates/foo.pt diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index e8d119e79..bc9d75d33 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -70,6 +70,11 @@ class TwillBase(unittest.TestCase): def setUp(self): import sys import twill + from twill.commands import config as twillconfig + # for benefit of Jython, which cannot import the subprocess module, we + # configure twill to not use tidy + twillconfig('use_tidy', False) + twillconfig('require_tidy', False) from pyramid.configuration import Configurator config = Configurator(root_factory=self.root_factory) config.load_zcml(self.config) @@ -79,7 +84,7 @@ class TwillBase(unittest.TestCase): else: out = open('/dev/null', 'wb') twill.set_output(out) - testing.setUp(registry=config.registry) + self.config = testing.setUp(registry=config.registry) def tearDown(self): import twill @@ -172,6 +177,10 @@ class TestRestBugApp(TwillBase): class TestViewDecoratorApp(TwillBase): config = 'pyramid.tests.viewdecoratorapp:configure.zcml' def test_it(self): + # we use mako here instead of chameleon because it works on Jython + tmpldir = os.path.join(os.path.dirname(__file__), 'viewdecoratorapp', + 'views') + self.config.registry.settings['mako.directories'] = tmpldir import twill.commands browser = twill.commands.get_browser() browser.go('http://localhost:6543/first') @@ -182,10 +191,6 @@ class TestViewDecoratorApp(TwillBase): self.assertEqual(browser.get_code(), 200) self.failUnless('OK2' in browser.get_html()) - browser.go('http://localhost:6543/third') - self.assertEqual(browser.get_code(), 200) - self.failUnless('OK3' in browser.get_html()) - class TestViewPermissionBug(TwillBase): # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html config = 'pyramid.tests.permbugapp:configure.zcml' diff --git a/pyramid/tests/viewdecoratorapp/views/templates/foo.mak b/pyramid/tests/viewdecoratorapp/views/templates/foo.mak new file mode 100644 index 000000000..6a2f701b6 --- /dev/null +++ b/pyramid/tests/viewdecoratorapp/views/templates/foo.mak @@ -0,0 +1,3 @@ + +${result} + diff --git a/pyramid/tests/viewdecoratorapp/views/templates/foo.pt b/pyramid/tests/viewdecoratorapp/views/templates/foo.pt deleted file mode 100644 index 6a2f701b6..000000000 --- a/pyramid/tests/viewdecoratorapp/views/templates/foo.pt +++ /dev/null @@ -1,3 +0,0 @@ - -${result} - diff --git a/pyramid/tests/viewdecoratorapp/views/views.py b/pyramid/tests/viewdecoratorapp/views/views.py index c59bc87ed..0b3147c86 100644 --- a/pyramid/tests/viewdecoratorapp/views/views.py +++ b/pyramid/tests/viewdecoratorapp/views/views.py @@ -1,17 +1,11 @@ -import os from pyramid.view import view_config -@view_config(renderer='templates/foo.pt', name='first') +@view_config(renderer='templates/foo.mak', name='first') def first(request): return {'result':'OK1'} -@view_config(renderer='pyramid.tests.viewdecoratorapp.views:templates/foo.pt', +@view_config(renderer='pyramid.tests.viewdecoratorapp.views:templates/foo.mak', name='second') def second(request): return {'result':'OK2'} -here = os.path.normpath(os.path.dirname(os.path.abspath(__file__))) -foo = os.path.join(here, 'templates', 'foo.pt') -@view_config(renderer=foo, name='third') -def third(request): - return {'result':'OK3'} -- cgit v1.2.3 From 35c5a3a77ff2f17aa06ae25032d2b2baeb75e096 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 16 Nov 2010 22:06:14 -0500 Subject: skip Chameleon tests on Jython --- pyramid/testing.py | 14 ++++++++++++++ pyramid/tests/test_chameleon_text.py | 15 +++++++++++++++ pyramid/tests/test_chameleon_zpt.py | 15 +++++++++++++++ pyramid/tests/test_testing.py | 24 ++++++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/pyramid/testing.py b/pyramid/testing.py index 4bc4728c0..deb6c8d8e 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -1,4 +1,5 @@ import copy +import os from zope.configuration.xmlconfig import _clearContext @@ -733,3 +734,16 @@ class MockTemplate(object): def __call__(self, *arg, **kw): self._received.update(kw) return self.response + +def skip_on(*platforms): + def decorator(func): + def wrapper(*args, **kw): + for platform in platforms: + if skip_on.os_name.startswith(platform): + return + return func(*args, **kw) + wrapper.__name__ = func.__name__ + wrapper.__doc__ = func.__doc__ + return wrapper + return decorator +skip_on.os_name = os.name # for testing diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index 027fa9474..654cfdf3b 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -1,6 +1,7 @@ import unittest from pyramid.testing import cleanUp +from pyramid.testing import skip_on class Base: def setUp(self): @@ -62,6 +63,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): from pyramid.interfaces import ITemplateRenderer verifyClass(ITemplateRenderer, self._getTargetClass()) + @skip_on('java') def test_template_reified(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -70,6 +72,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template, instance.__dict__['template']) + @skip_on('java') def test_template_with_ichameleon_translate(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -78,6 +81,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.translate, lookup.translate) + @skip_on('java') def test_template_with_debug_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -87,6 +91,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, True) + @skip_on('java') def test_template_with_reload_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -96,6 +101,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, True) + @skip_on('java') def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -105,6 +111,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, False) + @skip_on('java') def test_call(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -113,12 +120,14 @@ class TextTemplateRendererTests(Base, unittest.TestCase): self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') + @skip_on('java') def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) + @skip_on('java') def test_call_nonminimal(self): nonminimal = self._getTemplatePath('nonminimal.txt') lookup = DummyLookup() @@ -127,6 +136,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello, Chris!\n') + @skip_on('java') def test_implementation(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -140,6 +150,7 @@ class RenderTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_text import render_template return render_template(name, **kw) + @skip_on('java') def test_it(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) @@ -151,6 +162,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): from pyramid.chameleon_text import render_template_to_response return render_template_to_response(name, **kw) + @skip_on('java') def test_minimal(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) @@ -160,6 +172,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) + @skip_on('java') def test_iresponsefactory_override(self): from webob import Response class Response2(Response): @@ -175,6 +188,7 @@ class GetRendererTests(Base, unittest.TestCase): from pyramid.chameleon_text import get_renderer return get_renderer(name) + @skip_on('java') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: @@ -192,6 +206,7 @@ class GetTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_text import get_template return get_template(name) + @skip_on('java') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 056b95937..786e96129 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -1,6 +1,7 @@ import unittest from pyramid.testing import cleanUp +from pyramid.testing import skip_on class Base(object): def setUp(self): @@ -55,6 +56,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): from pyramid.interfaces import ITemplateRenderer verifyClass(ITemplateRenderer, self._getTargetClass()) + @skip_on('java') def test_call(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -64,6 +66,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): self.assertEqual(result, '
\n
') + @skip_on('java') def test_template_reified(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -72,6 +75,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template, instance.__dict__['template']) + @skip_on('java') def test_template_with_ichameleon_translate(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -80,6 +84,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.translate, lookup.translate) + @skip_on('java') def test_template_with_debug_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -89,6 +94,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, True) + @skip_on('java') def test_template_without_debug_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -98,6 +104,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, False) + @skip_on('java') def test_template_with_reload_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -107,6 +114,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, True) + @skip_on('java') def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -116,12 +124,14 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, False) + @skip_on('java') def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) + @skip_on('java') def test_implementation(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -137,6 +147,7 @@ class RenderTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import render_template return render_template(name, **kw) + @skip_on('java') def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) @@ -149,6 +160,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import render_template_to_response return render_template_to_response(name, **kw) + @skip_on('java') def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) @@ -159,6 +171,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) + @skip_on('java') def test_iresponsefactory_override(self): from webob import Response class Response2(Response): @@ -174,6 +187,7 @@ class GetRendererTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import get_renderer return get_renderer(name) + @skip_on('java') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: @@ -191,6 +205,7 @@ class GetTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import get_template return get_template(name) + @skip_on('java') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 8336bcec5..f05e7fc01 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -693,6 +693,30 @@ class TestMockTemplate(unittest.TestCase): template = self._makeOne('123') self.assertEqual(template(), '123') +class Test_skip_on(unittest.TestCase): + def setUp(self): + from pyramid.testing import skip_on + self.os_name = skip_on.os_name + skip_on.os_name = 'wrong' + + def tearDown(self): + from pyramid.testing import skip_on + skip_on.os_name = self.os_name + + def _callFUT(self, *platforms): + from pyramid.testing import skip_on + return skip_on(*platforms) + + def test_wrong_platform(self): + def foo(): return True + decorated = self._callFUT('wrong')(foo) + self.assertEqual(decorated(), None) + + def test_ok_platform(self): + def foo(): return True + decorated = self._callFUT('ok')(foo) + self.assertEqual(decorated(), True) + from zope.interface import Interface from zope.interface import implements -- cgit v1.2.3 From 1e00f3a5f3a1ae01999f99d412a35ec46abc827c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 00:33:42 -0500 Subject: - The ``pyramid_alchemy`` paster template had a typo, preventing an import from working. --- CHANGES.txt | 3 +++ pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2737e6893..c775bd422 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,9 @@ Bug Fixes - Add deprecation warning for import of ``pyramid.zcml.zcml_configure`` and ``pyramid.zcml.file_configure``. +- The ``pyramid_alchemy`` paster template had a typo, preventing an import + from working. + Backwards Incompatibilities --------------------------- diff --git a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl index a245bf141..748f58692 100755 --- a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl @@ -1,5 +1,5 @@ from pyramid.configuration import Configurator -from pramid.settings import asbool +from pyramid.settings import asbool from {{package}}.models import appmaker -- cgit v1.2.3 From 991a26169252e498e8e0576fc6d88514624b72d3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 00:44:33 -0500 Subject: fix test for jython --- pyramid/tests/test_renderers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 1dab39a6e..4c1971d04 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -137,10 +137,8 @@ class TestTemplateRendererFactory(unittest.TestCase): }) result = self._callFUT(info, factory) self.failUnless(result is renderer) - path = os.path.abspath(__file__) - if path.endswith('pyc'): # pragma: no cover - path = path[:-1] - self.assertEqual(factory.path, path) + path = os.path.abspath(__file__).split('$')[0] # jython + self.failUnless(factory.path.startswith(path)) self.assertEqual(factory.kw, {}) def test_reload_resources_true(self): -- cgit v1.2.3 From e6e0741dbfd90a8d6006bc195bfb5bdf8492ecfe Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 01:28:14 -0500 Subject: make test pass on jython --- pyramid/tests/test_authentication.py | 2 +- pyramid/tests/test_renderers.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 8bae18fba..69762fdb0 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -411,7 +411,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): def test_identify_cookie_reissue(self): import time - plugin = self._makeOne('secret', timeout=5, reissue_time=0) + plugin = self._makeOne('secret', timeout=5000, reissue_time=0) plugin.auth_tkt.timestamp = time.time() request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 4c1971d04..102a23f92 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -138,6 +138,8 @@ class TestTemplateRendererFactory(unittest.TestCase): result = self._callFUT(info, factory) self.failUnless(result is renderer) path = os.path.abspath(__file__).split('$')[0] # jython + if path.endswith('.pyc'): + path = path[:-1] self.failUnless(factory.path.startswith(path)) self.assertEqual(factory.kw, {}) -- cgit v1.2.3 From bfee0aa99963ec88ccb9cdf0b41f40e72ee371e4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 01:33:29 -0500 Subject: fix for jython --- pyramid/path.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyramid/path.py b/pyramid/path.py index 2b557af5f..10647c073 100644 --- a/pyramid/path.py +++ b/pyramid/path.py @@ -44,7 +44,8 @@ def package_of(pkg_or_module): def caller_package(level=2, caller_module=caller_module): # caller_module in arglist for tests module = caller_module(level+1) - if '__init__.py' in getattr(module, '__file__', ''): # empty at >>> + f = getattr(module, '__file__', '') + if (('__init__.py' in f) or ('__init__$py' in f)): # empty at >>> # Module is a package return module # Go up one level to get package -- cgit v1.2.3 From 03331655743a4826eaec56986720b9fa98c9ad13 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 02:06:36 -0500 Subject: gardening --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index 759b9bb70..ded470d78 100644 --- a/TODO.txt +++ b/TODO.txt @@ -118,4 +118,7 @@ Configurator.add_translation_dirs: not passed any context but a message, can't credibly be removed. +- Stop using Twill in integration tests (too much of a pain in the ass to + make work without deprecation warnings). +- Send patch to Ian for pastescript so it works on Jython. -- cgit v1.2.3 From 4f8c69f2ce04b5cb02b07c3891180cb46abd6aa2 Mon Sep 17 00:00:00 2001 From: d2m Date: Wed, 17 Nov 2010 19:02:51 +0100 Subject: paster templates now use the name "main" to represent the function that returns a WSGI application --- docs/tutorials/gae/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/gae/index.rst b/docs/tutorials/gae/index.rst index a2b190a31..9c8e8c07e 100644 --- a/docs/tutorials/gae/index.rst +++ b/docs/tutorials/gae/index.rst @@ -72,13 +72,13 @@ system. #. Edit ``config.py`` Edit the ``APP_NAME`` and ``APP_ARGS`` settings within - ``config.py``. The ``APP_NAME`` must be ``pyramidapp:app``, and + ``config.py``. The ``APP_NAME`` must be ``pyramidapp:main``, and the APP_ARGS must be ``({},)``. Any other settings in ``config.py`` should remain the same. .. code-block:: python - APP_NAME = 'pyramidapp:app' + APP_NAME = 'pyramidapp:main' APP_ARGS = ({},) #. Edit ``runner.py`` -- cgit v1.2.3 From 06c20074f0e3e624ba69ed6b6e4d0b17649ce9e6 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 17 Nov 2010 20:05:16 +0200 Subject: ReStructuredText fix in docstring of view_config. --- pyramid/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/view.py b/pyramid/view.py index ee2774aca..2ac51406e 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -177,7 +177,7 @@ class view_config(object): :class:`pyramid.view.bfg_view`. The following arguments are supported as arguments to - :class:`pyramid.view.view_config``: ``context``, ``permission``, + :class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``, ``request_type``, ``route_name``, ``request_method``, ``request_param``, ``containment``, ``xhr``, ``accept``, ``header`` and ``path_info``. -- cgit v1.2.3 From 8c6a4cea3ed0e588ffa395a99859cb1ac780360f Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 17 Nov 2010 20:38:00 +0200 Subject: More ReStructuredText fixes in docstrings. (After a recursive grep for ':[a-z]*:`[A-Z0-9a-z._]*``') --- docs/narr/hybrid.rst | 2 +- pyramid/configuration.py | 2 +- pyramid/interfaces.py | 2 +- pyramid/testing.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 61ac68d5d..b89d10c9f 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -358,7 +358,7 @@ Using the ``traverse`` Argument In a Route Definition Rather than using the ``*traverse`` remainder marker in a pattern, you can use the ``traverse`` argument to the -:meth:`pyramid.configuration.Configurator.add_route`` method. +:meth:`pyramid.configuration.Configurator.add_route` method. When you use the ``*traverse`` remainder marker, the traversal path is limited to being the remainder segments of a request URL when a route diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 3d1530406..3f959aabf 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -1559,7 +1559,7 @@ class Configurator(object): By default, ``categories`` is ``None`` which will execute *all* Venusian decorator callbacks including :app:`Pyramid`-related decorators such as - :class:`pyramid.view.view_config``. If this is not desirable + :class:`pyramid.view.view_config`. If this is not desirable because the codebase has other Venusian-using decorators that aren't meant to be invoked during a particular scan, use ``('pyramid',)`` as a ``categories`` value to limit the execution diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 47bccf71f..9b895e020 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -166,7 +166,7 @@ class IRequestFactory(Interface): def blank(path): """ Return an empty request object (see - :meth:`pyramid.request.Request.blank``)""" + :meth:`pyramid.request.Request.blank`)""" class IViewClassifier(Interface): """ *Internal only* marker interface for views.""" diff --git a/pyramid/testing.py b/pyramid/testing.py index deb6c8d8e..c6c999147 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -119,7 +119,7 @@ def registerTemplateRenderer(path, renderer=None): .. warning:: This API is deprecated as of :app:`Pyramid` 1.0. Instead use the - :meth:`pyramid.configuration.Configurator.testing_add_template`` + :meth:`pyramid.configuration.Configurator.testing_add_template` method in your unit and integration tests. """ @@ -151,7 +151,7 @@ def registerView(name, result='', view=None, for_=(Interface, Interface), .. warning:: This API is deprecated as of :app:`Pyramid` 1.0. Instead use the - :meth:`pyramid.configuration.Configurator.add_view`` + :meth:`pyramid.configuration.Configurator.add_view` method in your unit and integration tests. """ for_ = (IViewClassifier, ) + for_ -- cgit v1.2.3 From 54cf7cb2572f43bc70cfb9f7c156095de68d41e6 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Wed, 17 Nov 2010 21:07:02 +0200 Subject: Make 'make -C docs html' check out the pylons sphinx theme, if needed. --- docs/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 768efb9df..b74c55bd5 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -25,7 +25,7 @@ help: clean: -rm -rf _build/* -html: +html: _themes/ mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @@ -47,7 +47,7 @@ pickle: web: pickle -htmlhelp: +htmlhelp: _themes mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @@ -83,3 +83,6 @@ epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub @echo @echo "Build finished. The epub file is in _build/epub." + +_themes: + git clone git://github.com/Pylons/pylons_sphinx_theme.git _themes -- cgit v1.2.3 From 851957066a91278089b9a58f7493b0d1fb2903c6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 14:12:05 -0500 Subject: note marius' contributions --- docs/copyright.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/copyright.rst b/docs/copyright.rst index 64a0f819b..fa564a785 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -49,7 +49,8 @@ Attributions ------------ Contributors: - Ben Bangert, Blaise Laflamme, Carlos de la Guardia, Paul Everitt + Ben Bangert, Blaise Laflamme, Carlos de la Guardia, Paul Everitt, + Marius Gedminas .. Cover Designer: .. Nat Hardwick of `Electrosoup `_. -- cgit v1.2.3 From c7342c556303462fcdac54430be80a4d814e7869 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 15:22:06 -0500 Subject: gardening --- TODO.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index ded470d78..e7f8b2d92 100644 --- a/TODO.txt +++ b/TODO.txt @@ -121,4 +121,5 @@ - Stop using Twill in integration tests (too much of a pain in the ass to make work without deprecation warnings). -- Send patch to Ian for pastescript so it works on Jython. +- Use ``@register_view`` instead of ``@view_config`` and change view docs to + use "view registration" instead of "view configuration". -- cgit v1.2.3 From cb96e27f0a4c0bf730c5dfdd5eb7c0c8420e3c7d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 17:26:20 -0500 Subject: - Removed ``zodbsessions`` tutorial chapter. It's still useful, but we now have a SessionFactory abstraction which competes with it, and maintaining documentation on both ways to do it is a distraction. --- CHANGES.txt | 7 ++ docs/index.rst | 1 - docs/latexindex.rst | 1 - docs/tutorials/zodbsessions/index.rst | 189 ---------------------------------- 4 files changed, 7 insertions(+), 191 deletions(-) delete mode 100644 docs/tutorials/zodbsessions/index.rst diff --git a/CHANGES.txt b/CHANGES.txt index c775bd422..7f1390186 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -28,6 +28,13 @@ Deprecations ``settings`` attribute of the registry available from the request (``request.registry.settings``). +Documentation +------------- + +- Removed ``zodbsessions`` tutorial chapter. It's still useful, but we now + have a SessionFactory abstraction which competes with it, and maintaining + documentation on both ways to do it is a distraction. + 1.0a3 (2010-11-16) ================== diff --git a/docs/index.rst b/docs/index.rst index 4efb25dde..bfe956af2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -80,7 +80,6 @@ applications to various platforms. tutorials/gae/index.rst tutorials/modwsgi/index.rst tutorials/zeo/index.rst - tutorials/zodbsessions/index.rst tutorials/catalog/index.rst Reference Material diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 4efb193bd..388297de7 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -71,7 +71,6 @@ Tutorials tutorials/gae/index.rst tutorials/modwsgi/index.rst tutorials/zeo/index.rst - tutorials/zodbsessions/index.rst tutorials/catalog/index.rst .. _api_reference: diff --git a/docs/tutorials/zodbsessions/index.rst b/docs/tutorials/zodbsessions/index.rst deleted file mode 100644 index 9582e5de4..000000000 --- a/docs/tutorials/zodbsessions/index.rst +++ /dev/null @@ -1,189 +0,0 @@ -.. _zodb_sessions: - -Using ZODB-Based Sessions -========================= - -Sessions are server-side namespaces which are associated with a site -user that expire automatically after some period of disuse. - -If your application is ZODB-based (e.g. you've created an application -from the ``bfg_zodb`` paster template, or you've followed the -instructions in :ref:`zodb_with_zeo`), you can make use of the -``repoze.session`` and ``repoze.browserid`` packages to add -sessioning to your application. - -.. note:: You can use the ``repoze.session`` package even if your - application is not ZODB-based, but its backing store requires ZODB, - so it makes the most sense to use this package if your application - already uses ZODB. This tutorial does not cover usage of - ``repoze.session``-based sessions in applications that don't - already use ZODB. For this, see `the standalone repoze.session - usage documentation `_. - If you don't want to use ZODB to do sessioning, you might choose to - use a relational/filestorage sessioning system such as `Beaker - `_. :app:`Pyramid` is fully - compatible with this system too. - -Installing Dependencies ------------------------ - -#. Edit your :app:`Pyramid` application's ``setup.py`` file, adding - the following packages to the ``install_requires`` of the - application: - - - ``repoze.session`` - - - ``repoze.browserid`` - - For example, the relevant portion of your application's - ``setup.py`` file might look like so when you're finished adding - the dependencies. - - .. code-block:: python - :linenos: - - setup( - # ... other elements left out for brevity - install_requires=[ - 'pyramid', - 'repoze.folder', - 'repoze.retry', - 'repoze.tm2', - 'repoze.zodbconn', - 'repoze.session' - 'repoze.browserid', - ], - # ... other elements left out for brevity - ) - -#. Rerun your application's ``setup.py`` file (e.g. using ``python - setup.py develop``) to get these packages installed. - -Configuration -------------- - -#. Edit your application's Paste ``.ini`` file. - - If you already have an ``app`` section in the ``.ini`` file named - ``main``, rename this section to ``myapp`` (e.g. ``app:main`` -> - ``app:myapp``). Add a key to it named ``zodb_uri``, e.g. - - .. code-block:: python - :linenos: - - [app:myapp] - use = egg:myapp#app - zodb_uri = zeo://%(here)s/zeo.sock - reload_templates = true - debug_authorization = false - debug_notfound = false - - Add a ``filter`` section to the ``.ini`` file named "browserid": - - .. code-block:: python - :linenos: - - [filter:browserid] - use = egg:repoze.browserid#browserid - secret_key = my-secret-key - - Replace ``my-secret-key`` with any random string. This string - represents the value which the client-side "browser id" cookie is - encrypted with, to prevent tampering. - - If a ``pipeline`` named ``main`` does not already exist in the - paste ``.ini`` file , add a ``pipeline`` section named ``main``. - Put the names ``connector``, ``egg:repoze.retry#retry``, and - ``egg:repoze.tm2#tm`` to the top of the pipeline. - - .. code-block:: python - :linenos: - - [pipeline:main] - pipeline = - browserid - egg:repoze.retry#retry - egg:repoze.tm2#tm - myapp - - When you're finished, your ``.ini`` file might look like so: - - .. code-block:: ini - :linenos: - - [DEFAULT] - debug = true - - [app:myapp] - use = egg:myapp#app - zodb_uri = zeo://%(here)s/zeo.sock - reload_templates = true - debug_authorization = false - debug_notfound = false - - [filter:browserid] - use = egg:repoze.browserid#browserid - secret_key = my-secret-key - - [pipeline:main] - pipeline = - browserid - egg:repoze.retry#retry - egg:repoze.tm2#tm - myapp - - [server:main] - use = egg:Paste#http - host = 0.0.0.0 - port = 6543 - - See :ref:`MyProject_ini` for more information about project Paste - ``.ini`` files. - -#. Add a ``get_session`` API to your application. I've chosen to add - it directly to my ``views.py`` file, although it can live anywhere. - - .. code-block:: python - :linenos: - - from repoze.session.manager import SessionDataManager - from pyramid.traversal import find_root - - def get_session(context, request): - root = find_root(context) - if not hasattr(root, '_sessions'): - root._sessions = SessionDataManager(3600, 5) - session = root._sessions.get(request.environ['repoze.browserid']) - return session - - Note in the call to ``SessionDataManager`` that '3600' represents - the disuse timeout (60 minutes == 3600 seconds), and '5' represents - a write granularity time (the session will be marked as active at - most every five seconds). Vary these values as necessary. - -#. Whenever you want to use a session in your application, call this API: - - .. code-block:: python - :linenos: - - from repoze.session.manager import SessionDataManager - from pyramid.traversal import find_root - from pyramid.chameleon_zpt import render_template_to_response - - def my_view(context, request): - session = get_session(context, request) - session['abc'] = '123' - return render_template_to_response('templates/mytemplate.pt', - request = request, - project = 'sess') - - def get_session(context, request): - root = find_root(context) - if not hasattr(root, '_sessions'): - root._sessions = SessionDataManager(3600, 5) - session = root._sessions.get(request.environ['repoze.browserid']) - return session - -For more information, see the `repoze.session documentation -`_ and the `repoze.browserid -documentation `_. -- cgit v1.2.3 From ab1b77143424c73bf126126d1bc5ceecdbf05ddb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 17:27:21 -0500 Subject: gardening --- TODO.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index e7f8b2d92..03e42e438 100644 --- a/TODO.txt +++ b/TODO.txt @@ -77,9 +77,6 @@ - RendererHelper -> RendererInfo? -- Do something about ZODB session chapter: either remove or create a - pyramid_zodbsessions package. - - translationdir ZCML directive use of ``path_spec`` should maybe die. - Option for route_url to omit the host and port (perhaps a different -- cgit v1.2.3 From dbaa08e18d1bf50264faabb6e7dfce12f2c0ea71 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 18:11:48 -0500 Subject: - Replace Twill with WebTest in internal integration tests (avoid deprecation warnings generated by Twill). --- CHANGES.txt | 6 + TODO.txt | 3 - pyramid/tests/test_integration.py | 321 ++++++++++++++++++++------------------ setup.py | 4 +- 4 files changed, 174 insertions(+), 160 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7f1390186..fd0cead61 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -35,6 +35,12 @@ Documentation have a SessionFactory abstraction which competes with it, and maintaining documentation on both ways to do it is a distraction. +Internal +-------- + +- Replace Twill with WebTest in internal integration tests (avoid deprecation + warnings generated by Twill). + 1.0a3 (2010-11-16) ================== diff --git a/TODO.txt b/TODO.txt index 03e42e438..d8160eb67 100644 --- a/TODO.txt +++ b/TODO.txt @@ -115,8 +115,5 @@ Configurator.add_translation_dirs: not passed any context but a message, can't credibly be removed. -- Stop using Twill in integration tests (too much of a pain in the ass to - make work without deprecation warnings). - - Use ``@register_view`` instead of ``@view_config`` and change view docs to use "view registration" instead of "view configuration". diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index bc9d75d33..a502c2ff6 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -65,197 +65,208 @@ class TestStaticApp(unittest.TestCase): result.body, open(os.path.join(here, 'fixtures/minimal.pt'), 'r').read()) -class TwillBase(unittest.TestCase): +class IntegrationBase(unittest.TestCase): root_factory = None + ## def setUp(self): + ## import sys + ## import twill + ## from twill.commands import config as twillconfig + ## # for benefit of Jython, which cannot import the subprocess module, we + ## # configure twill to not use tidy + ## twillconfig('use_tidy', False) + ## twillconfig('require_tidy', False) + ## from pyramid.configuration import Configurator + ## config = Configurator(root_factory=self.root_factory) + ## config.load_zcml(self.config) + ## twill.add_wsgi_intercept('localhost', 6543, config.make_wsgi_app) + ## if sys.platform is 'win32': # pragma: no cover + ## out = open('nul:', 'wb') + ## else: + ## out = open('/dev/null', 'wb') + ## twill.set_output(out) + ## self.config = testing.setUp(registry=config.registry) + + ## def tearDown(self): + ## import twill + ## import twill.commands + ## twill.commands.reset_browser() + ## twill.remove_wsgi_intercept('localhost', 6543) + ## twill.set_output(None) + ## testing.tearDown() + def setUp(self): - import sys - import twill - from twill.commands import config as twillconfig - # for benefit of Jython, which cannot import the subprocess module, we - # configure twill to not use tidy - twillconfig('use_tidy', False) - twillconfig('require_tidy', False) from pyramid.configuration import Configurator config = Configurator(root_factory=self.root_factory) + config.begin() config.load_zcml(self.config) - twill.add_wsgi_intercept('localhost', 6543, config.make_wsgi_app) - if sys.platform is 'win32': # pragma: no cover - out = open('nul:', 'wb') - else: - out = open('/dev/null', 'wb') - twill.set_output(out) - self.config = testing.setUp(registry=config.registry) + app = config.make_wsgi_app() + from webtest import TestApp + self.testapp = TestApp(app) + self.config = config def tearDown(self): - import twill - import twill.commands - twill.commands.reset_browser() - twill.remove_wsgi_intercept('localhost', 6543) - twill.set_output(None) - testing.tearDown() - -class TestFixtureApp(TwillBase): + self.config.end() + +class TestFixtureApp(IntegrationBase): config = 'pyramid.tests.fixtureapp:configure.zcml' - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/another.html') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'fixture') - browser.go('http://localhost:6543') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'fixture') - browser.go('http://localhost:6543/dummyskin.html') - self.assertEqual(browser.get_code(), 404) - browser.go('http://localhost:6543/error.html') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'supressed') - browser.go('http://localhost:6543/protected.html') - self.assertEqual(browser.get_code(), 401) - -class TestCCBug(TwillBase): + def test_another(self): + res = self.testapp.get('/another.html', status=200) + self.assertEqual(res.body, 'fixture') + + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertEqual(res.body, 'fixture') + + def test_dummyskin(self): + self.testapp.get('/dummyskin.html', status=404) + + def test_error(self): + res = self.testapp.get('/error.html', status=200) + self.assertEqual(res.body, 'supressed') + + def test_protected(self): + self.testapp.get('/protected.html', status=401) + +class TestCCBug(IntegrationBase): # "unordered" as reported in IRC by author of # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/ config = 'pyramid.tests.ccbugapp:configure.zcml' - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/licenses/1/v1/rdf') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'rdf') - browser.go('http://localhost:6543/licenses/1/v1/juri') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'juri') - -class TestHybridApp(TwillBase): + def test_rdf(self): + res = self.testapp.get('/licenses/1/v1/rdf', status=200) + self.assertEqual(res.body, 'rdf') + + def test_juri(self): + res = self.testapp.get('/licenses/1/v1/juri', status=200) + self.assertEqual(res.body, 'juri') + +class TestHybridApp(IntegrationBase): # make sure views registered for a route "win" over views registered # without one, even though the context of the non-route view may # be more specific than the route view. config = 'pyramid.tests.hybridapp:configure.zcml' - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'global') - browser.go('http://localhost:6543/abc') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'route') - browser.go('http://localhost:6543/def') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'route2') - browser.go('http://localhost:6543/ghi') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'global') - browser.go('http://localhost:6543/jkl') - self.assertEqual(browser.get_code(), 404) - browser.go('http://localhost:6543/mno/global2') - self.assertEqual(browser.get_code(), 404) - browser.go('http://localhost:6543/pqr/global2') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'global2') - browser.go('http://localhost:6543/error') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'supressed') - browser.go('http://localhost:6543/error2') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'supressed2') - browser.go('http://localhost:6543/error_sub') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'supressed2') - -class TestRestBugApp(TwillBase): + def test_root(self): + res = self.testapp.get('/', status=200) + self.assertEqual(res.body, 'global') + + def test_abc(self): + res = self.testapp.get('/abc', status=200) + self.assertEqual(res.body, 'route') + + def test_def(self): + res = self.testapp.get('/def', status=200) + self.assertEqual(res.body, 'route2') + + def test_ghi(self): + res = self.testapp.get('/ghi', status=200) + self.assertEqual(res.body, 'global') + + def test_jkl(self): + self.testapp.get('/jkl', status=404) + + def test_mno(self): + self.testapp.get('/mno', status=404) + + def test_pqr_global2(self): + res = self.testapp.get('/pqr/global2', status=200) + self.assertEqual(res.body, 'global2') + + def test_error(self): + res = self.testapp.get('/error', status=200) + self.assertEqual(res.body, 'supressed') + + def test_error2(self): + res = self.testapp.get('/error2', status=200) + self.assertEqual(res.body, 'supressed2') + + def test_error_sub(self): + res = self.testapp.get('/error_sub', status=200) + self.assertEqual(res.body, 'supressed2') + +class TestRestBugApp(IntegrationBase): # test bug reported by delijati 2010/2/3 (http://pastebin.com/d4cc15515) config = 'pyramid.tests.restbugapp:configure.zcml' def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/pet') - self.assertEqual(browser.get_code(), 200) - self.assertEqual(browser.get_html(), 'gotten') + res = self.testapp.get('/pet', status=200) + self.assertEqual(res.body, 'gotten') -class TestViewDecoratorApp(TwillBase): +class TestViewDecoratorApp(IntegrationBase): config = 'pyramid.tests.viewdecoratorapp:configure.zcml' - def test_it(self): - # we use mako here instead of chameleon because it works on Jython + def _configure_mako(self): tmpldir = os.path.join(os.path.dirname(__file__), 'viewdecoratorapp', 'views') self.config.registry.settings['mako.directories'] = tmpldir - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/first') - self.assertEqual(browser.get_code(), 200) - self.failUnless('OK' in browser.get_html()) - browser.go('http://localhost:6543/second') - self.assertEqual(browser.get_code(), 200) - self.failUnless('OK2' in browser.get_html()) + def test_first(self): + # we use mako here instead of chameleon because it works on Jython + self._configure_mako() + res = self.testapp.get('/first', status=200) + self.failUnless('OK' in res.body) + + def test_second(self): + # we use mako here instead of chameleon because it works on Jython + self._configure_mako() + res = self.testapp.get('/second', status=200) + self.failUnless('OK2' in res.body) -class TestViewPermissionBug(TwillBase): +class TestViewPermissionBug(IntegrationBase): # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html config = 'pyramid.tests.permbugapp:configure.zcml' - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/test') - self.assertEqual(browser.get_code(), 200) - self.failUnless('ACLDenied' in browser.get_html()) - browser.go('http://localhost:6543/x') - self.assertEqual(browser.get_code(), 401) - -class TestDefaultViewPermissionBug(TwillBase): + def test_test(self): + res = self.testapp.get('/test', status=200) + self.failUnless('ACLDenied' in res.body) + + def test_x(self): + self.testapp.get('/x', status=401) + +class TestDefaultViewPermissionBug(IntegrationBase): # default_view_permission bug as reported by Wiggy at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003602.html config = 'pyramid.tests.defpermbugapp:configure.zcml' - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/x') - self.assertEqual(browser.get_code(), 401) - self.failUnless('failed permission check' in browser.get_html()) - browser.go('http://localhost:6543/y') - self.assertEqual(browser.get_code(), 401) - self.failUnless('failed permission check' in browser.get_html()) - browser.go('http://localhost:6543/z') - self.assertEqual(browser.get_code(), 200) - self.failUnless('public' in browser.get_html()) + def test_x(self): + res = self.testapp.get('/x', status=401) + self.failUnless('failed permission check' in res.body) + + def test_y(self): + res = self.testapp.get('/y', status=401) + self.failUnless('failed permission check' in res.body) + + def test_z(self): + res = self.testapp.get('/z', status=200) + self.failUnless('public' in res.body) from pyramid.tests.exceptionviewapp.models import AnException, NotAnException excroot = {'anexception':AnException(), 'notanexception':NotAnException()} -class TestExceptionViewsApp(TwillBase): +class TestExceptionViewsApp(IntegrationBase): config = 'pyramid.tests.exceptionviewapp:configure.zcml' root_factory = lambda *arg: excroot - def test_it(self): - import twill.commands - browser = twill.commands.get_browser() - browser.go('http://localhost:6543/') - self.assertEqual(browser.get_code(), 200) - self.failUnless('maybe' in browser.get_html()) - - browser.go('http://localhost:6543/notanexception') - self.assertEqual(browser.get_code(), 200) - self.failUnless('no' in browser.get_html()) - - browser.go('http://localhost:6543/anexception') - self.assertEqual(browser.get_code(), 200) - self.failUnless('yes' in browser.get_html()) - - browser.go('http://localhost:6543/route_raise_exception') - self.assertEqual(browser.get_code(), 200) - self.failUnless('yes' in browser.get_html()) - - browser.go('http://localhost:6543/route_raise_exception2') - self.assertEqual(browser.get_code(), 200) - self.failUnless('yes' in browser.get_html()) - - browser.go('http://localhost:6543/route_raise_exception3') - self.assertEqual(browser.get_code(), 200) - self.failUnless('whoa' in browser.get_html()) - - browser.go('http://localhost:6543/route_raise_exception4') - self.assertEqual(browser.get_code(), 200) - self.failUnless('whoa' in browser.get_html()) + def test_root(self): + res = self.testapp.get('/', status=200) + self.failUnless('maybe' in res.body) + + def test_notanexception(self): + res = self.testapp.get('/notanexception', status=200) + self.failUnless('no' in res.body) + + def test_anexception(self): + res = self.testapp.get('/anexception', status=200) + self.failUnless('yes' in res.body) + + def test_route_raise_exception(self): + res = self.testapp.get('/route_raise_exception', status=200) + self.failUnless('yes' in res.body) + + def test_route_raise_exception2(self): + res = self.testapp.get('/route_raise_exception2', status=200) + self.failUnless('yes' in res.body) + + def test_route_raise_exception3(self): + res = self.testapp.get('/route_raise_exception3', status=200) + self.failUnless('whoa' in res.body) + + def test_route_raise_exception4(self): + res = self.testapp.get('/route_raise_exception4', status=200) + self.failUnless('whoa' in res.body) class DummyContext(object): pass diff --git a/setup.py b/setup.py index b220d2914..c2d11384f 100644 --- a/setup.py +++ b/setup.py @@ -45,10 +45,10 @@ install_requires=[ ] if platform.system() == 'Java': - tests_require = install_requires + ['twill'] + tests_require = install_requires + ['WebTest'] else: tests_require= install_requires + ['Sphinx', 'docutils', 'coverage', - 'twill', 'repoze.sphinx.autointerface'] + 'WebTest', 'repoze.sphinx.autointerface'] if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') -- cgit v1.2.3 From 20fa5619608f55ab85d431de4d806f7f337c5384 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 18:13:21 -0500 Subject: remove unused code --- pyramid/tests/test_integration.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index a502c2ff6..c70985e79 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -7,8 +7,6 @@ from pyramid.view import static from zope.interface import Interface -from pyramid import testing - class INothing(Interface): pass @@ -67,33 +65,6 @@ class TestStaticApp(unittest.TestCase): class IntegrationBase(unittest.TestCase): root_factory = None - ## def setUp(self): - ## import sys - ## import twill - ## from twill.commands import config as twillconfig - ## # for benefit of Jython, which cannot import the subprocess module, we - ## # configure twill to not use tidy - ## twillconfig('use_tidy', False) - ## twillconfig('require_tidy', False) - ## from pyramid.configuration import Configurator - ## config = Configurator(root_factory=self.root_factory) - ## config.load_zcml(self.config) - ## twill.add_wsgi_intercept('localhost', 6543, config.make_wsgi_app) - ## if sys.platform is 'win32': # pragma: no cover - ## out = open('nul:', 'wb') - ## else: - ## out = open('/dev/null', 'wb') - ## twill.set_output(out) - ## self.config = testing.setUp(registry=config.registry) - - ## def tearDown(self): - ## import twill - ## import twill.commands - ## twill.commands.reset_browser() - ## twill.remove_wsgi_intercept('localhost', 6543) - ## twill.set_output(None) - ## testing.tearDown() - def setUp(self): from pyramid.configuration import Configurator config = Configurator(root_factory=self.root_factory) -- cgit v1.2.3 From 2c9d148493aaa977c69e77072466685a3367ad0b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 18:48:42 -0500 Subject: - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling ``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty string. - Add a ``pyramid.request.Request.route_path`` API. This is a convenience method of the request which calls ``pyramid.url.route_url``. --- CHANGES.txt | 11 +++++++++++ TODO.txt | 5 ----- docs/api/url.rst | 2 ++ pyramid/request.py | 43 ++++++++++++++++++++++++++++++++++++++++++- pyramid/tests/test_request.py | 17 +++++++++++++++++ pyramid/tests/test_url.py | 21 +++++++++++++++++++++ pyramid/url.py | 27 +++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fd0cead61..604f28cf4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,17 @@ Next release ============ +Features +-------- + +- Add a ``pyramid.url.route_path`` API, allowing folks to generate relative + URLs. Calling ``route_path`` is the same as calling + ``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty + string. + +- Add a ``pyramid.request.Request.route_path`` API. This is a convenience + method of the request which calls ``pyramid.url.route_url``. + Bug Fixes --------- diff --git a/TODO.txt b/TODO.txt index d8160eb67..def6fe687 100644 --- a/TODO.txt +++ b/TODO.txt @@ -44,8 +44,6 @@ action = '^foo$' mypackage.views.MyView.foo_GET -- Ability to use configurator as a context manager. - - Provide a response_cookies attribute on the request for rendered responses that can be used as input to response.set_cookie. @@ -79,9 +77,6 @@ - translationdir ZCML directive use of ``path_spec`` should maybe die. -- Option for route_url to omit the host and port (perhaps a different - function named ``route_path``). - - SQLAlchemy idiomatics: mcdonc: those paster templates all look pretty good... the diff --git a/docs/api/url.rst b/docs/api/url.rst index 71987498a..8c702a3fb 100644 --- a/docs/api/url.rst +++ b/docs/api/url.rst @@ -9,6 +9,8 @@ .. autofunction:: route_url + .. autofunction:: route_path + .. autofunction:: static_url .. autofunction:: urlencode diff --git a/pyramid/request.py b/pyramid/request.py index dff70d93c..43a4a3aa2 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -11,6 +11,7 @@ from pyramid.decorator import reify from pyramid.url import model_url from pyramid.url import route_url from pyramid.url import static_url +from pyramid.url import route_path class TemplateContext(object): pass @@ -246,13 +247,53 @@ class Request(WebobRequest): like this:: from pyramid.url import static_url - static_url('mypackage:static/foo.css, request) + static_url('mypackage:static/foo.css', request) See :func:`pyramid.url.static_url` for more information """ return static_url(path, self, **kw) + def route_path(self, route_name, *elements, **kw): + """Generates a path (aka a 'relative URL', a URL minus the host, + scheme, and port) for a named :app:`Pyramid` + :term:`route configuration`. + + .. note:: Calling :meth:`pyramid.Request.route_path` can be used to + achieve the same result as :func:`pyramid.url.route_path`. + + This is a convenience method. The result of calling + :meth:`pyramid.request.Request.route_path` is the same as calling + :func:`pyramid.url.route_path` with an explicit ``request`` + parameter. + + This method accepts the same arguments as + :meth:`pyramid.request.Request.route_url` and performs the same duty. + It just omits the host, port, and scheme information in the return + value; only the path, query parameters, and anchor data are present + in the returned string. + + The :meth:`pyramid.request.Request.route_path` method calls the + :func:`pyramid.url.route_path` function using the Request object as + the ``request`` argument. The ``*elements`` and ``*kw`` arguments + passed to :meth:`pyramid.request.Request.route_path` are passed + through to :func:`pyramid.url.route_path` unchanged and its result is + returned. + + This call to :meth:`pyramid.request.Request.route_path`:: + + request.route_path('foobar') + + Is completely equivalent to calling :func:`pyramid.url.route_path` + like this:: + + from pyramid.url import route_path + route_path('foobar', request) + + See :func:`pyramid.url.route_path` for more information + """ + return route_path(route_name, self, *elements, **kw) + # override default WebOb "environ['adhoc_attr']" mutation behavior __getattr__ = object.__getattribute__ __setattr__ = object.__setattr__ diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index a398bf3af..ab985694b 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -266,6 +266,23 @@ class TestRequest(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') + def test_route_path(self): + environ = { + 'PATH_INFO':'/', + 'SERVER_NAME':'example.com', + 'SERVER_PORT':'5432', + 'QUERY_STRING':'la=La%20Pe%C3%B1a', + 'wsgi.url_scheme':'http', + } + from pyramid.interfaces import IRoutesMapper + inst = self._makeOne(environ) + mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) + self.config.registry.registerUtility(mapper, IRoutesMapper) + result = inst.route_path('flub', 'extra1', 'extra2', + a=1, b=2, c=3, _query={'a':1}, + _anchor=u"foo") + self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') + def test_static_url(self): from pyramid.interfaces import IStaticURLInfo environ = { diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 332ff3f11..aad969ca7 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -209,6 +209,27 @@ class TestRouteUrl(unittest.TestCase): self.assertEqual(result, 'http://example2.com/1/2/3/a') self.assertEqual(route.kw, {}) # shouldnt have anchor/query +class TestRoutePath(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, *arg, **kw): + from pyramid.url import route_path + return route_path(*arg, **kw) + + def test_with_elements(self): + from pyramid.interfaces import IRoutesMapper + request = _makeRequest() + mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) + request.registry.registerUtility(mapper, IRoutesMapper) + result = self._callFUT('flub', request, 'extra1', 'extra2', + a=1, b=2, c=3, _query={'a':1}, + _anchor=u"foo") + self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') + class TestStaticUrl(unittest.TestCase): def setUp(self): cleanUp() diff --git a/pyramid/url.py b/pyramid/url.py index fa15e6364..fca2582de 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -153,6 +153,33 @@ def route_url(route_name, request, *elements, **kw): return app_url + path + suffix + qs + anchor +def route_path(route_name, request, *elements, **kw): + """Generates a path (aka a 'relative URL', a URL minus the host, scheme, + and port) for a named :app:`Pyramid` :term:`route configuration`. + + .. note:: Calling :meth:`pyramid.Request.route_path` can be used to + achieve the same result as :func:`pyramid.url.route_path`. + + This function accepts the same argument as :func:`pyramid.url.route_url` + and performs the same duty. It just omits the host, port, and scheme + information in the return value; only the path, query parameters, + and anchor data are present in the returned string. + + For example, if you've defined a route named 'foobar' with the path + ``/:foo/:bar``, this call to ``route_path``:: + + route_path('foobar', request, foo='1', bar='2') + + Will return the string ``/1/2``. + + .. note:: Calling ``route_path('route', request)`` is the same as calling + ``route_url('route', request, _app_url='')``. ``route_path`` is, in + fact, implemented in terms of ``route_url`` in just this way. As a + result, passing ``_app_url`` within the ``**kw`` values passed to + ``route_path`` will result in an exception. + """ + return route_url(route_name, request, *elements, _app_url='', **kw) + def model_url(model, request, *elements, **kw): """ Generate a string representing the absolute URL of the ``model`` -- cgit v1.2.3 From 50fb1030b7491d5430d03a71a44e152180f22bc3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 19:00:28 -0500 Subject: suppress deprecation warnings while building docs --- docs/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index b4448b803..81096da3b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,9 @@ import sys, os import datetime +import warnings + +warnings.simplefilter('ignore', DeprecationWarning) # skip raw nodes from sphinx.writers.text import TextTranslator -- cgit v1.2.3 From 647815ef5fb2ca795b2d4ceb8f6740acefb7c695 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 19:06:27 -0500 Subject: fix route_url on Jython --- pyramid/url.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyramid/url.py b/pyramid/url.py index fca2582de..76d95689c 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -175,10 +175,11 @@ def route_path(route_name, request, *elements, **kw): .. note:: Calling ``route_path('route', request)`` is the same as calling ``route_url('route', request, _app_url='')``. ``route_path`` is, in fact, implemented in terms of ``route_url`` in just this way. As a - result, passing ``_app_url`` within the ``**kw`` values passed to - ``route_path`` will result in an exception. + result, any ``_app_url`` pass within the ``**kw`` values to + ``route_path`` will be ignored. """ - return route_url(route_name, request, *elements, _app_url='', **kw) + kw['_app_url'] = '' + return route_url(route_name, request, *elements, **kw) def model_url(model, request, *elements, **kw): """ -- cgit v1.2.3 From 8964ac5a6c041430efcc5bc9d40a13a2d472c968 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 19:59:25 -0500 Subject: get rid of extraneous note --- pyramid/request.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 43a4a3aa2..2f9ca5819 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -259,9 +259,6 @@ class Request(WebobRequest): scheme, and port) for a named :app:`Pyramid` :term:`route configuration`. - .. note:: Calling :meth:`pyramid.Request.route_path` can be used to - achieve the same result as :func:`pyramid.url.route_path`. - This is a convenience method. The result of calling :meth:`pyramid.request.Request.route_path` is the same as calling :func:`pyramid.url.route_path` with an explicit ``request`` -- cgit v1.2.3 From 485ef6938f8314567ad49c16589de9e31fac383f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 21:58:55 -0500 Subject: gardening --- TODO.txt | 137 +++++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/TODO.txt b/TODO.txt index def6fe687..959f16af9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,81 @@ :mod:`repoze.bfg` TODOs ======================= +Must-Have (before 1.0) +---------------------- + +- Test on GAE, Jython, PyPy, IronPython. + +- Add docs for httpexceptions module for each webob.exc class that inherits + from WSGIHTTPException. + +- Add a ``handler`` ZCML directive. This implies some slightly dicey + refactoring of the configurator to allow it to generate ZCML + "discriminators" for views and routes. + +- Provide a .flash API on session object. + +- Make default renderer work (renderer registered with no name, which is + active for every view unless the view names a specific renderer). + +- Use ``@register_view`` instead of ``@view_config`` and change view docs to + use "view registration" instead of "view configuration". + +- Remove calls to config.begin()/config.end() from startup config code in + tutorials and paster templates (no longer required). + +- SQLAlchemy idiomatics: + + mcdonc: those paster templates all look pretty good... the + only thing i'd consider is adjusting your config variable names to match + exactly what sqlalchemy uses as parameter names, see here: + http://www.sqlalchemy.org/docs/core/engines.html + + mcdonc: especially in the pylons_sqla ini file, where the db + initialization is mixed in w/ the app config... + + ... i'd use "sqlalchemy.PARAMETER" for all of the sqla + settings, so it could easily be handed to engine_from_config w/o any need + to parse by hand + + mcdonc: in the other ini files, where sqlalchemy is given its + own part, the "sqlalchemy." prefix probably isn't necessary, but matching + the parameter names (e.g. 'url' instead of 'db_string') is still probably + a good idea + +- Non-bwcompat use of threadlocals that need to be documented or ameliorated: + + security.principals_allowed_by_permission + + resource.OverrideProvider._get_overrides: can't credibly be removed, + because it stores an overrideprovider as a module-scope global. + + traversal.traverse: this API is a stepchild, and needs to be changed. + + Configurator.add_translation_dirs: not passed any context but a message, + can't credibly be removed. + +- Better ``config.add_handler`` documentation. + +Should-Have +----------- + +- Create a ``docs`` directory for each paster template. + +- Remove "BFG" from Pyramid-specific environ variables. + +- translationdir ZCML directive use of ``path_spec`` should maybe die. + +- Add CRSF token creation/checking machinery (only "should have" vs. "must + have" because I'm not sure it belongs in Pyramid.. it definitely must exist + in formgen libraries, and *might* belong in Pyramid). + +- Change "Cleaning up After a Request" in the urldispatch chapter to + use ``request.add_response_callback``. + +Nice-to-Have +------------ + - Supply ``X-Vhm-Host`` support. - Basic WSGI documentation (pipeline / app / server). @@ -50,65 +125,7 @@ - Raise an exception when a value in response_headerlist is not a string or decide to encode. -- Change "Cleaning up After a Request" in the urldispatch chapter to - use ``request.add_response_callback``. - -- Update App engine chapter. - -- Browser id? - -- .flash API on session. - -- CRSF token machinery - -- ``add_handler`` documentation. - -- ``handler`` ZCML directive. - -- ``docs`` directory for each paster template. - -- "BFG" in environ variables. - -- Test on GAE, Jython, PyPy, IronPython. - -- Add docs for httpexceptions. - -- RendererHelper -> RendererInfo? - -- translationdir ZCML directive use of ``path_spec`` should maybe die. - -- SQLAlchemy idiomatics: - - mcdonc: those paster templates all look pretty good... the - only thing i'd consider is adjusting your config variable names to match - exactly what sqlalchemy uses as parameter names, see here: - http://www.sqlalchemy.org/docs/core/engines.html - - mcdonc: especially in the pylons_sqla ini file, where the db - initialization is mixed in w/ the app config... - - ... i'd use "sqlalchemy.PARAMETER" for all of the sqla - settings, so it could easily be handed to engine_from_config w/o any need - to parse by hand - - mcdonc: in the other ini files, where sqlalchemy is given its - own part, the "sqlalchemy." prefix probably isn't necessary, but matching - the parameter names (e.g. 'url' instead of 'db_string') is still probably - a good idea - -- Default renderer. +- Update App engine chapter with less creaky directions. -- Non-bwcompat use of threadlocals: +- Add functionality that mocks the behavior of ``repoze.browserid``. - security.principals_allowed_by_permission - - resource.OverrideProvider._get_overrides: can't credibly be removed, - because it stores an overrideprovider as a module-scope global. - - traversal.traverse: this API is a stepchild, and needs to be changed. - - Configurator.add_translation_dirs: not passed any context but a message, - can't credibly be removed. - -- Use ``@register_view`` instead of ``@view_config`` and change view docs to - use "view registration" instead of "view configuration". -- cgit v1.2.3 From 0511437d5250d249accda26ba6435ab737f8c0c5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 17 Nov 2010 21:59:21 -0500 Subject: not BFG no mo --- TODO.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.txt b/TODO.txt index 959f16af9..f12dcee73 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,5 @@ -:mod:`repoze.bfg` TODOs -======================= +Pyramid TODOs +============= Must-Have (before 1.0) ---------------------- -- cgit v1.2.3 From 34c59071c28e18aeec87c7a244e38ed22e2f35c6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 03:27:44 -0500 Subject: ocd break: sort imports --- pyramid/configuration.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 3f959aabf..63b1f70d7 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -24,6 +24,8 @@ from pyramid.interfaces import IChameleonTranslate from pyramid.interfaces import IDebugLogger from pyramid.interfaces import IDefaultPermission from pyramid.interfaces import IDefaultRootFactory +from pyramid.interfaces import IException +from pyramid.interfaces import IExceptionResponse from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import ILocaleNegotiator from pyramid.interfaces import IMultiView @@ -36,34 +38,32 @@ from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ISecuredView +from pyramid.interfaces import ISessionFactory from pyramid.interfaces import IStaticURLInfo from pyramid.interfaces import ITranslationDirectories from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IExceptionResponse -from pyramid.interfaces import IException -from pyramid.interfaces import ISessionFactory from pyramid import chameleon_text from pyramid import chameleon_zpt -from pyramid.mako_templating import renderer_factory as mako_renderer_factory from pyramid import renderers -from pyramid.renderers import RendererHelper from pyramid.authorization import ACLAuthorizationPolicy from pyramid.compat import all from pyramid.compat import md5 from pyramid.events import ApplicationCreated +from pyramid.exceptions import ConfigurationError from pyramid.exceptions import Forbidden from pyramid.exceptions import NotFound from pyramid.exceptions import PredicateMismatch -from pyramid.exceptions import ConfigurationError from pyramid.i18n import get_localizer from pyramid.log import make_stream_logger +from pyramid.mako_templating import renderer_factory as mako_renderer_factory from pyramid.path import caller_package -from pyramid.path import package_path from pyramid.path import package_of +from pyramid.path import package_path from pyramid.registry import Registry +from pyramid.renderers import RendererHelper from pyramid.request import route_request_iface from pyramid.resource import PackageOverrides from pyramid.resource import resolve_resource_spec @@ -72,12 +72,12 @@ from pyramid.static import StaticURLInfo from pyramid.threadlocal import get_current_registry from pyramid.threadlocal import get_current_request from pyramid.threadlocal import manager -from pyramid.traversal import traversal_path from pyramid.traversal import DefaultRootFactory from pyramid.traversal import find_interface +from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper -from pyramid.view import render_view_to_response from pyramid.view import default_exceptionresponse_view +from pyramid.view import render_view_to_response MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() -- cgit v1.2.3 From 85ee02b24caf0bf6b491b5ecf7ad84c29ca8c75c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 04:21:22 -0500 Subject: - Make test suite pass on Jython (requires PasteScript trunk, presumably to be 1.7.4). --- CHANGES.txt | 3 +++ pyramid/tests/test_configuration.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 604f28cf4..14b2c569d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,9 @@ Features - Add a ``pyramid.request.Request.route_path`` API. This is a convenience method of the request which calls ``pyramid.url.route_url``. +- Make test suite pass on Jython (requires PasteScript trunk, presumably to + be 1.7.4). + Bug Fixes --------- diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index ded17cb33..0d8905f7c 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -2940,6 +2940,7 @@ class ConfiguratorTests(unittest.TestCase): pyramid.tests) def test_scan_integration(self): + import os from zope.interface import alsoProvides from pyramid.interfaces import IRequest from pyramid.view import render_view_to_response @@ -3011,8 +3012,12 @@ class ConfiguratorTests(unittest.TestCase): result = render_view_to_response(ctx, req, 'another_stacked_class2') self.assertEqual(result, 'another_stacked_class') - self.assertRaises(TypeError, - render_view_to_response, ctx, req, 'basemethod') + if not os.name.startswith('java'): + # on Jython, a class without an __init__ apparently accepts + # any number of arguments without raising a TypeError. + + self.assertRaises(TypeError, + render_view_to_response, ctx, req, 'basemethod') result = render_view_to_response(ctx, req, 'method1') self.assertEqual(result, 'method1') -- cgit v1.2.3 From e57f5c2dd29f6bd88158a4b84ef67694fd33ba47 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 04:41:50 -0500 Subject: fix race condition test failure (seen on Jython) --- pyramid/authentication.py | 6 +++++- pyramid/tests/test_authentication.py | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 4849d2c41..86d725bcf 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -286,6 +286,7 @@ EXPIRE = object() class AuthTktCookieHelper(object): auth_tkt = auth_tkt # for tests + now = None # for tests userid_type_decoders = { 'int':int, @@ -373,7 +374,10 @@ class AuthTktCookieHelper(object): except self.auth_tkt.BadTicket: return None - now = time.time() + now = self.now # service tests + + if now is None: + now = time.time() if self.timeout and ( (timestamp + self.timeout) < now ): return None diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 69762fdb0..d9d0c2c97 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -411,8 +411,10 @@ class TestAuthTktCookieHelper(unittest.TestCase): def test_identify_cookie_reissue(self): import time - plugin = self._makeOne('secret', timeout=5000, reissue_time=0) - plugin.auth_tkt.timestamp = time.time() + plugin = self._makeOne('secret', timeout=10, reissue_time=0) + now = time.time() + plugin.auth_tkt.timestamp = now + plugin.now = now + 1 request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) self.failUnless(result) -- cgit v1.2.3 From 165020a7ba21d0bbebaaefd142b70c28d278ea83 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 09:42:17 -0500 Subject: gardening --- TODO.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index f12dcee73..fe411f749 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,11 +4,6 @@ Pyramid TODOs Must-Have (before 1.0) ---------------------- -- Test on GAE, Jython, PyPy, IronPython. - -- Add docs for httpexceptions module for each webob.exc class that inherits - from WSGIHTTPException. - - Add a ``handler`` ZCML directive. This implies some slightly dicey refactoring of the configurator to allow it to generate ZCML "discriminators" for views and routes. @@ -60,6 +55,11 @@ Must-Have (before 1.0) Should-Have ----------- +- Try to make test suite pass on PyPy, IronPython. + +- Add docs for httpexceptions module for each webob.exc class that inherits + from WSGIHTTPException. + - Create a ``docs`` directory for each paster template. - Remove "BFG" from Pyramid-specific environ variables. -- cgit v1.2.3 From 3b913e3f8a2cb4afbe57033643fd998cdde0217b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 09:42:41 -0500 Subject: add pypyenv, change ignores for things that are directories to end in a slash --- .gitignore | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 5d46de01f..706f6493d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,10 @@ *.pt.py *.txt.py .coverage -env26 -env24 -env27 -jyenv +env26/ +env24/ +env27/ +jyenv/ +pypyenv/ build/ dist/ -- cgit v1.2.3 From 1e467e1bacc915d1e00bdce189e35f5afb568132 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 09:43:18 -0500 Subject: we dont actually need coverage (and it fails on pypy) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c2d11384f..c1939f73c 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ install_requires=[ if platform.system() == 'Java': tests_require = install_requires + ['WebTest'] else: - tests_require= install_requires + ['Sphinx', 'docutils', 'coverage', + tests_require= install_requires + ['Sphinx', 'docutils', 'WebTest', 'repoze.sphinx.autointerface'] if sys.version_info[:2] < (2, 6): -- cgit v1.2.3 From a66593d25e77f1a0e749f5590b45498bbaa66755 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 16:56:05 -0500 Subject: - Fix apparent failures when calling ``pyramid.traversal.find_model(root, path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is (erroneously) a Unicode object. The user is meant to pass these APIs a string object, never a Unicode object. In practice, however, users indeed pass Unicode. Because the string that is passed must be ASCII encodeable, now, if they pass a Unicode object, its data is eagerly converted to an ASCII string rather than being passed along to downstream code as a convenience to the user and to prevent puzzling second-order failures from cropping up (all failures will occur within ``pyramid.traversal.traverse`` rather than later down the line as the result of calling ``traversal_path``). --- CHANGES.txt | 12 ++++++++++++ pyramid/tests/test_traversal.py | 26 ++++++++++++++++++++++++++ pyramid/traversal.py | 13 ++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 14b2c569d..0720034b2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -28,6 +28,18 @@ Bug Fixes - The ``pyramid_alchemy`` paster template had a typo, preventing an import from working. +- Fix apparent failures when calling ``pyramid.traversal.find_model(root, + path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is + (erroneously) a Unicode object. The user is meant to pass these APIs a + string object, never a Unicode object. In practice, however, users indeed + pass Unicode. Because the string that is passed must be ASCII encodeable, + now, if they pass a Unicode object, its data is eagerly converted to an + ASCII string rather than being passed along to downstream code as a + convenience to the user and to prevent puzzling second-order failures from + cropping up (all failures will occur within ``pyramid.traversal.traverse`` + rather than later down the line as the result of calling + ``traversal_path``). + Backwards Incompatibilities --------------------------- diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py index 2deb5982c..3245d6302 100644 --- a/pyramid/tests/test_traversal.py +++ b/pyramid/tests/test_traversal.py @@ -522,6 +522,32 @@ class FindModelTests(unittest.TestCase): self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') + def test_absolute_unicode_found(self): + # test for bug wiggy found in wild, traceback stack: + # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' + # wiggy's code: section=find_model(page, root) + # find_model L76: D = traverse(model, path) + # traverse L291: return traverser(request) + # __call__ line 568: vpath_tuple = traversal_path(vpath) + # lru_cached line 91: f(*arg) + # traversal_path line 443: path.encode('ascii') + # UnicodeEncodeError: 'ascii' codec can't encode characters in + # position 1-12: ordinal not in range(128) + # + # solution: encode string to ascii in pyramid.traversal.traverse + # before passing it along to webob as path_info + from pyramid.traversal import ModelGraphTraverser + unprintable = DummyContext() + root = DummyContext(unprintable) + unprintable.__parent__ = root + unprintable.__name__ = unicode( + '/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8') + root.__parent__ = None + root.__name__ = None + traverser = ModelGraphTraverser + self._registerTraverser(traverser) + result = self._callFUT(root, u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') + self.assertEqual(result, unprintable) class ModelPathTests(unittest.TestCase): def _callFUT(self, model, *elements): diff --git a/pyramid/traversal.py b/pyramid/traversal.py index e928c33f7..fb73ad906 100644 --- a/pyramid/traversal.py +++ b/pyramid/traversal.py @@ -228,7 +228,7 @@ def traverse(model, path): object supplied to the function as the ``model`` argument. If an empty string is passed as ``path``, the ``model`` passed in will be returned. Model path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and as + manner: each Unicode path segment must be encoded as UTF-8 and each path segment must escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The @@ -272,6 +272,17 @@ def traverse(model, path): else: path = '' + # The user is supposed to pass us a string object, never Unicode. In + # practice, however, users indeed pass Unicode to this API. If they do + # pass a Unicode object, its data *must* be entirely encodeable to ASCII, + # so we encode it here as a convenience to the user and to prevent + # second-order failures from cropping up (all failures will occur at this + # step rather than later down the line as the result of calling + # ``traversal_path``). + + if isinstance(path, unicode): + path = path.encode('ascii') + if path and path[0] == '/': model = find_root(model) -- cgit v1.2.3 From e84116c068f45c68752f89062fa545dd40acd63f Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 14:13:02 -0800 Subject: - URL Dispatch now allows for replacement markers to be located anywhere in the pattern, instead of immediately following a ``/``. - Added ``marker_pattern`` option to ``add_route`` to supply a dict of regular expressions to be used for markers in the pattern instead of the default regular expression that matched everything except a ``/``. --- CHANGES.txt | 5 +++++ docs/narr/urldispatch.rst | 39 +++++++++++++++++++++++++++++---------- pyramid/configuration.py | 12 ++++++++++-- pyramid/tests/test_urldispatch.py | 13 ++++++++++--- pyramid/urldispatch.py | 20 +++++++++++--------- 5 files changed, 65 insertions(+), 24 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 604f28cf4..b418565fa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Next release Features -------- +- URL Dispatch now allows for replacement markers to be located anywhere + in the pattern, instead of immediately following a ``/``. +- Added ``marker_pattern`` option to ``add_route`` to supply a dict of + regular expressions to be used for markers in the pattern instead of the + default regular expression that matched everything except a ``/``. - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling ``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 4442be355..0170b36e2 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -208,16 +208,16 @@ and: /:foo/bar/baz -A patttern segment (an individual item between ``/`` characters in the -pattern) may either be a literal string (e.g. ``foo``) *or* it may be -a segment replacement marker (e.g. ``:foo``) or a certain combination -of both. +A pattern segment (an individual item between ``/`` characters in the pattern) +may either be a literal string (e.g. ``foo``) *or* it may be a replacement +marker (e.g. ``:foo``) or a certain combination of both. A replacement marker +does not need to be preceded by a ``/`` character. -A segment replacement marker is in the format ``:name``, where this -means "accept any characters up to the next nonalphaunumeric character +A replacement marker is in the format ``:name``, where this +means "accept any characters up to the next non-alphanumeric character and use this as the ``name`` matchdict value." For example, the following pattern defines one literal segment ("foo") and two dynamic -segments ("baz", and "bar"): +replacement markers ("baz", and "bar"): .. code-block:: text @@ -252,9 +252,21 @@ literal path ``/foo/biz`` will not match, because it does not contain a literal ``.html`` at the end of the segment represented by ``:name.html`` (it only contains ``biz``, not ``biz.html``). -This does not mean, however, that you can use two segment replacement -markers in the same segment. For instance, ``/:foo:bar`` is a -nonsensical route pattern. It will never match anything. +To capture both segments, two replacement markers can be used: + +.. code-block:: text + + foo/:name.:ext + +The literal path ``/foo/biz.html`` will match the above route pattern, and the +match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs because +the replacement marker ``:name`` has a literal part of ``.`` between the other +replacement marker ``:ext``. + +It is possible to use two replacement markers without any literal characters +between them, for instance ``/:foo:bar``. This would be a nonsensical pattern +without specifying any ``pattern_regexes`` to restrict valid values of each +replacement marker. Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL ``/abc/``: @@ -471,6 +483,13 @@ represent neither predicates nor view configuration information. as ``path``. ``path`` continues to work as an alias for ``pattern``. +``marker_pattern`` + A dict of regular expression replacements for replacement markers in the + pattern to use when generating the complete regular expression used to + match the route. By default, every replacement marker in the pattern is + replaced with the regular expression ``[^/]+``. Values in this dict will + be used instead if present. + ``xhr`` This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`request` must possess an diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 3f959aabf..41b774e65 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -1188,6 +1188,7 @@ class Configurator(object): def add_route(self, name, pattern=None, + marker_pattern=None, view=None, view_for=None, permission=None, @@ -1306,7 +1307,13 @@ class Configurator(object): to this function will be used to represent the pattern value if the ``pattern`` argument is ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` wins. - + + marker_pattern + + A dict of regular expression's that will be used in the place + of the default ``[^/]+`` regular expression for all replacement + markers in the route pattern. + xhr This value should be either ``True`` or ``False``. If this @@ -1529,7 +1536,8 @@ class Configurator(object): raise ConfigurationError('"pattern" argument may not be None') return mapper.connect(name, pattern, factory, predicates=predicates, - pregenerator=pregenerator) + pregenerator=pregenerator, + marker_pattern=marker_pattern) def get_routes_mapper(self): """ Return the :term:`routes mapper` object associated with diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py index 799f4986f..f77ebbbb4 100644 --- a/pyramid/tests/test_urldispatch.py +++ b/pyramid/tests/test_urldispatch.py @@ -218,9 +218,9 @@ class RoutesMapperTests(unittest.TestCase): self.assertEqual(mapper.generate('abc', {}), 123) class TestCompileRoute(unittest.TestCase): - def _callFUT(self, pattern): + def _callFUT(self, pattern, marker_pattern=None): from pyramid.urldispatch import _compile_route - return _compile_route(pattern) + return _compile_route(pattern, marker_pattern) def test_no_star(self): matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar') @@ -251,6 +251,14 @@ class TestCompileRoute(unittest.TestCase): from pyramid.exceptions import URLDecodeError matcher, generator = self._callFUT('/:foo') self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00') + + def test_custom_regex(self): + matcher, generator = self._callFUT('foo/:baz/biz/:buz.:bar', + {'buz': '[^/\.]+'}) + self.assertEqual(matcher('/foo/baz/biz/buz.bar'), + {'baz':'baz', 'buz':'buz', 'bar':'bar'}) + self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) + self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}), '/foo/1/biz/2.html') class TestCompileRouteMatchFunctional(unittest.TestCase): def matches(self, pattern, path, expected): @@ -271,7 +279,6 @@ class TestCompileRouteMatchFunctional(unittest.TestCase): self.matches('/:x', '', None) self.matches('/:x', '/', None) self.matches('/abc/:def', '/abc/', None) - self.matches('/abc/:def:baz', '/abc/bleep', None) # bad pattern self.matches('', '/', {}) self.matches('/', '/', {}) self.matches('/:x', '/a', {'x':'a'}) diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index aa0bddfe9..06ec647e5 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -17,10 +17,10 @@ _marker = object() class Route(object): implements(IRoute) def __init__(self, name, pattern, factory=None, predicates=(), - pregenerator=None): + pregenerator=None, marker_pattern=None): self.pattern = pattern self.path = pattern # indefinite b/w compat, not in interface - self.match, self.generate = _compile_route(pattern) + self.match, self.generate = _compile_route(pattern, marker_pattern) self.name = name self.factory = factory self.predicates = predicates @@ -42,11 +42,12 @@ class RoutesMapper(object): return self.routes.get(name) def connect(self, name, pattern, factory=None, predicates=(), - pregenerator=None): + pregenerator=None, marker_pattern=None): if name in self.routes: oldroute = self.routes[name] self.routelist.remove(oldroute) - route = Route(name, pattern, factory, predicates, pregenerator) + route = Route(name, pattern, factory, predicates, pregenerator, + marker_pattern) self.routelist.append(route) self.routes[name] = route return route @@ -74,8 +75,9 @@ class RoutesMapper(object): return {'route':None, 'match':None} # stolen from bobo and modified -route_re = re.compile(r'(/:[a-zA-Z]\w*)') -def _compile_route(route): +route_re = re.compile(r'(:[a-zA-Z]\w*)') +def _compile_route(route, marker_pattern=None): + marker_pattern = marker_pattern or {} if not route.startswith('/'): route = '/' + route star = None @@ -91,9 +93,9 @@ def _compile_route(route): gen.append(prefix) while pat: name = pat.pop() - name = name[2:] - gen.append('/%%(%s)s' % name) - name = '/(?P<%s>[^/]+)' % name + name = name[1:] + gen.append('%%(%s)s' % name) + name = '(?P<%s>%s)' % (name, marker_pattern.get(name, '[^/]+')) rpat.append(name) s = pat.pop() if s: -- cgit v1.2.3 From 47c9f884f72e6902c3b789ab9c4c08a6fabcfc6a Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 14:13:33 -0800 Subject: Gardening. --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4d51b89f5..dbd774086 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,9 +6,11 @@ Features - URL Dispatch now allows for replacement markers to be located anywhere in the pattern, instead of immediately following a ``/``. + - Added ``marker_pattern`` option to ``add_route`` to supply a dict of regular expressions to be used for markers in the pattern instead of the default regular expression that matched everything except a ``/``. + - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling ``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty -- cgit v1.2.3 From 29b7aabcb7b50024ead27cb5ff9cb47757ffe462 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 18 Nov 2010 22:26:41 -0500 Subject: - Make test suite pass on PyPy (Chameleon doesn't work). --- CHANGES.txt | 2 ++ pyramid/configuration.py | 19 +++++++++++++++---- pyramid/testing.py | 7 +++++++ pyramid/tests/test_chameleon_text.py | 30 ++++++++++++++++-------------- pyramid/tests/test_chameleon_zpt.py | 30 ++++++++++++++++-------------- pyramid/tests/test_configuration.py | 10 ++++++++-- pyramid/tests/test_renderers.py | 2 +- 7 files changed, 65 insertions(+), 35 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index dbd774086..c1e89734d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -22,6 +22,8 @@ Features - Make test suite pass on Jython (requires PasteScript trunk, presumably to be 1.7.4). +- Make test suite pass on PyPy (Chameleon doesn't work). + Bug Fixes --------- diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 543f8f82d..d861292b6 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -45,8 +45,15 @@ from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid import chameleon_text -from pyramid import chameleon_zpt +try: + from pyramid import chameleon_text +except TypeError: # pragma: no cover + chameleon_text = None # pypy +try: + from pyramid import chameleon_zpt +except TypeError: # pragma: no cover + chameleon_zpt = None # pypy + from pyramid import renderers from pyramid.authorization import ACLAuthorizationPolicy from pyramid.compat import all @@ -83,14 +90,18 @@ MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() DEFAULT_RENDERERS = ( - ('.pt', chameleon_zpt.renderer_factory), - ('.txt', chameleon_text.renderer_factory), ('.mak', mako_renderer_factory), ('.mako', mako_renderer_factory), ('json', renderers.json_renderer_factory), ('string', renderers.string_renderer_factory), ) +if chameleon_text: + DEFAULT_RENDERERS += (('.pt', chameleon_zpt.renderer_factory),) +if chameleon_zpt: + DEFAULT_RENDERERS += (('.txt', chameleon_text.renderer_factory),) + + class Configurator(object): """ A Configurator is used to configure a :app:`Pyramid` diff --git a/pyramid/testing.py b/pyramid/testing.py index c6c999147..4acede879 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -741,9 +741,16 @@ def skip_on(*platforms): for platform in platforms: if skip_on.os_name.startswith(platform): return + if platform == 'pypy' and skip_on.pypy: # pragma: no cover + return return func(*args, **kw) wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ return wrapper return decorator skip_on.os_name = os.name # for testing +try: # pragma: no cover + import __pypy__ + skip_on.pypy = True +except ImportError: + skip_on.pypy = False diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index 654cfdf3b..9486bbc88 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -51,6 +51,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): klass = self._getTargetClass() return klass(*arg, **kw) + @skip_on('pypy') def test_instance_implements_ITemplate(self): from zope.interface.verify import verifyObject from pyramid.interfaces import ITemplateRenderer @@ -58,12 +59,13 @@ class TextTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() verifyObject(ITemplateRenderer, self._makeOne(path, lookup)) + @skip_on('pypy') def test_class_implements_ITemplate(self): from zope.interface.verify import verifyClass from pyramid.interfaces import ITemplateRenderer verifyClass(ITemplateRenderer, self._getTargetClass()) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_reified(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -72,7 +74,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template, instance.__dict__['template']) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_ichameleon_translate(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -81,7 +83,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.translate, lookup.translate) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_debug_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -91,7 +93,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, True) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_reload_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -101,7 +103,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, True) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -111,7 +113,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, False) - @skip_on('java') + @skip_on('java', 'pypy') def test_call(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -120,14 +122,14 @@ class TextTemplateRendererTests(Base, unittest.TestCase): self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello.\n') - @skip_on('java') + @skip_on('java', 'pypy') def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) - @skip_on('java') + @skip_on('java', 'pypy') def test_call_nonminimal(self): nonminimal = self._getTemplatePath('nonminimal.txt') lookup = DummyLookup() @@ -136,7 +138,7 @@ class TextTemplateRendererTests(Base, unittest.TestCase): self.failUnless(isinstance(result, str)) self.assertEqual(result, 'Hello, Chris!\n') - @skip_on('java') + @skip_on('java', 'pypy') def test_implementation(self): minimal = self._getTemplatePath('minimal.txt') lookup = DummyLookup() @@ -150,7 +152,7 @@ class RenderTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_text import render_template return render_template(name, **kw) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) @@ -162,7 +164,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): from pyramid.chameleon_text import render_template_to_response return render_template_to_response(name, **kw) - @skip_on('java') + @skip_on('java', 'pypy') def test_minimal(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) @@ -172,7 +174,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) - @skip_on('java') + @skip_on('java', 'pypy') def test_iresponsefactory_override(self): from webob import Response class Response2(Response): @@ -188,7 +190,7 @@ class GetRendererTests(Base, unittest.TestCase): from pyramid.chameleon_text import get_renderer return get_renderer(name) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: @@ -206,7 +208,7 @@ class GetTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_text import get_template return get_template(name) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 786e96129..802f4460f 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -44,6 +44,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): klass = self._getTargetClass() return klass(*arg, **kw) + @skip_on('pypy') def test_instance_implements_ITemplate(self): from zope.interface.verify import verifyObject from pyramid.interfaces import ITemplateRenderer @@ -51,12 +52,13 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): lookup = DummyLookup() verifyObject(ITemplateRenderer, self._makeOne(path, lookup)) + @skip_on('pypy') def test_class_implements_ITemplate(self): from zope.interface.verify import verifyClass from pyramid.interfaces import ITemplateRenderer verifyClass(ITemplateRenderer, self._getTargetClass()) - @skip_on('java') + @skip_on('java', 'pypy') def test_call(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -66,7 +68,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): self.assertEqual(result, '
\n
') - @skip_on('java') + @skip_on('java', 'pypy') def test_template_reified(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -75,7 +77,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template, instance.__dict__['template']) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_ichameleon_translate(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -84,7 +86,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.translate, lookup.translate) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_debug_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -94,7 +96,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, True) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_without_debug_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -104,7 +106,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.debug, False) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_with_reload_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -114,7 +116,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, True) - @skip_on('java') + @skip_on('java', 'pypy') def test_template_without_reload_templates(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -124,14 +126,14 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): template = instance.template self.assertEqual(template.auto_reload, False) - @skip_on('java') + @skip_on('java', 'pypy') def test_call_with_nondict_value(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() instance = self._makeOne(minimal, lookup) self.assertRaises(ValueError, instance, None, {}) - @skip_on('java') + @skip_on('java', 'pypy') def test_implementation(self): minimal = self._getTemplatePath('minimal.pt') lookup = DummyLookup() @@ -147,7 +149,7 @@ class RenderTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import render_template return render_template(name, **kw) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) @@ -160,7 +162,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import render_template_to_response return render_template_to_response(name, **kw) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) @@ -171,7 +173,7 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) - @skip_on('java') + @skip_on('java', 'pypy') def test_iresponsefactory_override(self): from webob import Response class Response2(Response): @@ -187,7 +189,7 @@ class GetRendererTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import get_renderer return get_renderer(name) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: @@ -205,7 +207,7 @@ class GetTemplateTests(Base, unittest.TestCase): from pyramid.chameleon_zpt import get_template return get_template(name) - @skip_on('java') + @skip_on('java', 'pypy') def test_it(self): from pyramid.interfaces import IRendererFactory class Dummy: diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index 0d8905f7c..a8ea63f54 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -2,6 +2,11 @@ import unittest from pyramid import testing +try: + import __pypy__ +except: + __pypy__ = None + class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.configuration import Configurator @@ -90,8 +95,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.package, this_pkg) self.failUnless(config.registry.getUtility(IRendererFactory, 'json')) self.failUnless(config.registry.getUtility(IRendererFactory, 'string')) - self.failUnless(config.registry.getUtility(IRendererFactory, '.pt')) - self.failUnless(config.registry.getUtility(IRendererFactory, '.txt')) + if not __pypy__: + self.failUnless(config.registry.getUtility(IRendererFactory, '.pt')) + self.failUnless(config.registry.getUtility(IRendererFactory,'.txt')) self.failUnless(config.registry.getUtility(IRendererFactory, '.mak')) self.failUnless(config.registry.getUtility(IRendererFactory, '.mako')) diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 102a23f92..bd6a0825d 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -138,7 +138,7 @@ class TestTemplateRendererFactory(unittest.TestCase): result = self._callFUT(info, factory) self.failUnless(result is renderer) path = os.path.abspath(__file__).split('$')[0] # jython - if path.endswith('.pyc'): + if path.endswith('.pyc'): # pragma: no cover path = path[:-1] self.failUnless(factory.path.startswith(path)) self.assertEqual(factory.kw, {}) -- cgit v1.2.3 From 4018adea20ee73cfeeaebb69e95e4b00dd5cf22e Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 20:38:25 -0800 Subject: - URL Dispatch now uses the form ``{marker}`` to denote a replace marker in the route pattern instead of ``:marker``. The old syntax is still backwards compatible and accepted. The new format allows a regular expression for that marker location to be used instead of the default ``[^/]+``, for example ``{marker:\d+}`` is now valid to require the marker to be digits. --- CHANGES.txt | 8 ++- docs/narr/urldispatch.rst | 140 +++++++++++++++++++++++++------------- pyramid/configuration.py | 10 +-- pyramid/tests/test_urldispatch.py | 7 +- pyramid/urldispatch.py | 30 +++++--- 5 files changed, 121 insertions(+), 74 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index dbd774086..8aff366a6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,9 +7,11 @@ Features - URL Dispatch now allows for replacement markers to be located anywhere in the pattern, instead of immediately following a ``/``. -- Added ``marker_pattern`` option to ``add_route`` to supply a dict of - regular expressions to be used for markers in the pattern instead of the - default regular expression that matched everything except a ``/``. +- URL Dispatch now uses the form ``{marker}`` to denote a replace marker in + the route pattern instead of ``:marker``. The old syntax is still backwards + compatible and accepted. The new format allows a regular expression for that + marker location to be used instead of the default ``[^/]+``, for example + ``{marker:\d+}`` is now valid to require the marker to be digits. - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 0170b36e2..0d66f28a1 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -92,7 +92,17 @@ registry`. Here's an example: # pyramid.configuration.Configurator class; "myview" is assumed # to be a "view callable" function from views import myview - config.add_route('myroute', '/prefix/:one/:two', view=myview) + config.add_route('myroute', '/prefix/{one}/{two}', view=myview) + +.. versionchanged:: 1.0a4 + + Prior to 1.0a4, routes allow for a marker starting with a ``:``, for + example:: + + config.add_route('myroute', '/prefix/:one/:two', view=myview) + + Starting in 1.0a4, this style is deprecated in favor or ``{}`` usage + which allows for additional functionality. .. index:: single: route configuration; view callable @@ -116,7 +126,7 @@ Here's an example route configuration that references a view callable: # pyramid.configuration.Configurator class; "myview" is assumed # to be a "view callable" function from myproject.views import myview - config.add_route('myroute', '/prefix/:one/:two', view=myview) + config.add_route('myroute', '/prefix/{one}/{two}', view=myview) You can also pass a :term:`dotted Python name` as the ``view`` argument rather than an actual callable: @@ -128,7 +138,7 @@ rather than an actual callable: # pyramid.configuration.Configurator class; "myview" is assumed # to be a "view callable" function from myproject.views import myview - config.add_route('myroute', '/prefix/:one/:two', + config.add_route('myroute', '/prefix/{one}/{two}', view='myproject.views.myview') When a route configuration names a ``view`` attribute, the :term:`view @@ -200,20 +210,20 @@ the following patterns are equivalent: .. code-block:: text - :foo/bar/baz + {foo}/bar/baz and: .. code-block:: text - /:foo/bar/baz + /{foo}/bar/baz A pattern segment (an individual item between ``/`` characters in the pattern) may either be a literal string (e.g. ``foo``) *or* it may be a replacement -marker (e.g. ``:foo``) or a certain combination of both. A replacement marker +marker (e.g. ``{foo}``) or a certain combination of both. A replacement marker does not need to be preceded by a ``/`` character. -A replacement marker is in the format ``:name``, where this +A replacement marker is in the format ``{name}``, where this means "accept any characters up to the next non-alphanumeric character and use this as the ``name`` matchdict value." For example, the following pattern defines one literal segment ("foo") and two dynamic @@ -221,7 +231,7 @@ replacement markers ("baz", and "bar"): .. code-block:: text - foo/:baz/:bar + foo/{baz}/{bar} The above pattern will match these URLs, generating the following matchdicts: @@ -244,27 +254,27 @@ pattern. So, for instance, if this route pattern was used: .. code-block:: text - foo/:name.html + foo/{name}.html The literal path ``/foo/biz.html`` will match the above route pattern, and the match result will be ``{'name':u'biz'}``. However, the literal path ``/foo/biz`` will not match, because it does not contain a literal ``.html`` at the end of the segment represented by -``:name.html`` (it only contains ``biz``, not ``biz.html``). +``{name}.html`` (it only contains ``biz``, not ``biz.html``). To capture both segments, two replacement markers can be used: .. code-block:: text - foo/:name.:ext + foo/{name}.{ext} The literal path ``/foo/biz.html`` will match the above route pattern, and the match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs because -the replacement marker ``:name`` has a literal part of ``.`` between the other +the replacement marker ``{name}`` has a literal part of ``.`` between the other replacement marker ``:ext``. It is possible to use two replacement markers without any literal characters -between them, for instance ``/:foo:bar``. This would be a nonsensical pattern +between them, for instance ``/{foo}{bar}``. This would be a nonsensical pattern without specifying any ``pattern_regexes`` to restrict valid values of each replacement marker. @@ -282,7 +292,7 @@ pattern: .. code-block:: text - foo/:bar + foo/{bar} When matching the following URL: @@ -304,7 +314,7 @@ need to be preceded by a slash. For example: .. code-block:: text - foo/:baz/:bar*fizzle + foo/{baz}/{bar}*fizzle The above pattern will match these URLs, generating the following matchdicts: @@ -336,6 +346,24 @@ Will generate the following matchdict: {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')} +By default, the ``*stararg`` will parse the remainder sections into a tuple +split by segment. Changing the regular expression used to match a marker can +also capture the remainder of the URL, for example: + +.. code-block:: text + + foo/{baz}/{bar}{fizzle:.*} + +The above pattern will match these URLs, generating the following matchdicts: + + foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':()} + foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c')} + +This occurs because the default regular expression for a marker is ``[^/]+`` +which will match everything up to the first ``/``, while ``{filzzle:.*}`` will +result in a regular expression match of ``.*`` capturing the remainder into +a single value. + .. index:: single: route ordering @@ -360,12 +388,12 @@ be added in the following order: .. code-block:: text - members/:def + members/{def} members/abc In such a configuration, the ``members/abc`` pattern would *never* be matched; this is because the match ordering will always match -``members/:def`` first; the route configuration with ``members/abc`` +``members/{def}`` first; the route configuration with ``members/abc`` will never be evaluated. .. index:: @@ -446,8 +474,8 @@ represent neither predicates nor view configuration information. The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided is - ``articles/:article/edit``, and the ``traverse`` argument provided - is ``/:article``, when a request comes in that causes the route to + ``articles/{article}/edit``, and the ``traverse`` argument provided + is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is '1' (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's @@ -474,7 +502,7 @@ represent neither predicates nor view configuration information. **Predicate Arguments** ``pattern`` - The path of the route e.g. ``ideas/:idea``. This argument is + The path of the route e.g. ``ideas/{idea}``. This argument is required. See :ref:`route_path_pattern_syntax` for information about the syntax of route paths. If the path doesn't match the current URL, route matching continues. @@ -482,13 +510,6 @@ represent neither predicates nor view configuration information. .. note:: In earlier releases of this framework, this argument existed as ``path``. ``path`` continues to work as an alias for ``pattern``. - -``marker_pattern`` - A dict of regular expression replacements for replacement markers in the - pattern to use when generating the complete regular expression used to - match the route. By default, every replacement marker in the pattern is - replaced with the regular expression ``[^/]+``. Values in this dict will - be used instead if present. ``xhr`` This value should be either ``True`` or ``False``. If this value is @@ -663,7 +684,7 @@ match. For example: num_one_two_or_three = any_of('num', 'one', 'two', 'three') - config.add_route('num', '/:num', + config.add_route('num', '/{num}', custom_predicates=(num_one_two_or_three,)) The above ``any_of`` function generates a predicate which ensures that @@ -694,7 +715,7 @@ For instance, a predicate might do some type conversion of values: ymd_to_int = integers('year', 'month', 'day') - config.add_route('num', '/:year/:month/:day', + config.add_route('num', '/{year}/{month}/{day}', custom_predicates=(ymd_to_int,)) Note that a conversion predicate is still a predicate so it must @@ -702,6 +723,29 @@ return ``True`` or ``False``; a predicate that does *only* conversion, such as the one we demonstrate above should unconditionally return ``True``. +To avoid the try/except uncertainty, the route pattern can contain regular +expressions specifying requirements for that marker. For instance: + +.. code-block:: python + :linenos: + + def integers(*segment_names): + def predicate(info, request): + match = info['match'] + for segment_name in segment_names: + match[segment_name] = int(match[segment_name]) + return True + return predicate + + ymd_to_int = integers('year', 'month', 'day') + + config.add_route('num', '/{year:\d+}/{month:\d+}/{day:\d+}', + custom_predicates=(ymd_to_int,)) + +Now the try/except is no longer needed because the route will not match at +all unless these markers match ``\d+`` which requires them to be valid digits +for an ``int`` type conversion. + The ``match`` dictionary passed within ``info`` to each predicate attached to a route will be the same dictionary. Therefore, when registering a custom predicate which modifies the ``match`` dict, the @@ -732,9 +776,9 @@ An example of using the route in a set of route predicates: if info['route'].name in ('ymd', 'ym', 'y'): return info['match']['year'] == '2010' - config.add_route('y', '/:year', custom_predicates=(twenty_ten,)) - config.add_route('ym', '/:year/:month', custom_predicates=(twenty_ten,)) - config.add_route('ymd', '/:year/:month:/day', + config.add_route('y', '/{year}', custom_predicates=(twenty_ten,)) + config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,)) + config.add_route('ymd', '/{year}/{month}/{day}', custom_predicates=(twenty_ten,)) The above predicate, when added to a number of route configurations @@ -833,7 +877,7 @@ The simplest route declaration which configures a route match to .. code-block:: python :linenos: - config.add_route('idea', 'site/:id', view='mypackage.views.site_view') + config.add_route('idea', 'site/{id}', view='mypackage.views.site_view') When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *pattern* of the route @@ -841,12 +885,12 @@ configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. In the case of the above example, when the URL of a request matches -``/site/:id``, the view callable at the Python dotted path name +``/site/{id}``, the view callable at the Python dotted path name ``mypackage.views.site_view`` will be called with the request. In other words, we've associated a view callable directly with a route pattern. -When the ``/site/:id`` route pattern matches during a request, the +When the ``/site/{id}`` route pattern matches during a request, the ``site_view`` view callable is invoked with that request as its sole argument. When this route matches, a ``matchdict`` will be generated and attached to the request as ``request.matchdict``. If the specific @@ -879,30 +923,30 @@ might add to your application: .. code-block:: python :linenos: - config.add_route('idea', 'ideas/:idea', view='mypackage.views.idea_view') - config.add_route('user', 'users/:user', view='mypackage.views.user_view') - config.add_route('tag', 'tags/:tags', view='mypackage.views.tag_view') + config.add_route('idea', 'ideas/{idea}', view='mypackage.views.idea_view') + config.add_route('user', 'users/{user}', view='mypackage.views.user_view') + config.add_route('tag', 'tags/{tags}', view='mypackage.views.tag_view') The above configuration will allow :app:`Pyramid` to service URLs in these forms: .. code-block:: text - /ideas/:idea - /users/:user - /tags/:tag + /ideas/{idea} + /users/{user} + /tags/{tag} -- When a URL matches the pattern ``/ideas/:idea``, the view callable +- When a URL matches the pattern ``/ideas/{idea}``, the view callable available at the dotted Python pathname ``mypackage.views.idea_view`` will be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'idea':'1'}``. -- When a URL matches the pattern ``/users/:user``, the view callable +- When a URL matches the pattern ``/users/{user}``, the view callable available at the dotted Python pathname ``mypackage.views.user_view`` will be called. For the specific URL ``/users/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'user':'1'}``. -- When a URL matches the pattern ``/tags/:tag``, the view callable available +- When a URL matches the pattern ``/tags/{tag}``, the view callable available at the dotted Python pathname ``mypackage.views.tag_view`` will be called. For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'tag':'1'}``. @@ -930,7 +974,7 @@ An example of using a route with a factory: .. code-block:: python :linenos: - config.add_route('idea', 'ideas/:idea', + config.add_route('idea', 'ideas/{idea}', view='myproject.views.idea_view', factory='myproject.models.Idea') @@ -958,7 +1002,7 @@ a ``view`` declaration. .. code-block:: python :linenos: - config.add_route('idea', 'site/:id') + config.add_route('idea', 'site/{id}') config.add_view(route_name='idea', view='mypackage.views.site_view') This set of configuration parameters creates a configuration @@ -968,7 +1012,7 @@ completely equivalent to this example provided in .. code-block:: python :linenos: - config.add_route('idea', 'site/:id', view='mypackage.views.site_view') + config.add_route('idea', 'site/{id}', view='mypackage.views.site_view') In fact, the spelling which names a ``view`` attribute is just syntactic sugar for the more verbose spelling which contains separate @@ -1009,7 +1053,7 @@ Generating Route URLs Use the :func:`pyramid.url.route_url` function to generate URLs based on route patterns. For example, if you've configured a route with the ``name`` -"foo" and the ``pattern`` ":a/:b/:c", you might do this. +"foo" and the ``pattern`` "{a}/{b}/{c}", you might do this. .. ignore-next-block .. code-block:: python diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 543f8f82d..c1e4088fd 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -1188,7 +1188,6 @@ class Configurator(object): def add_route(self, name, pattern=None, - marker_pattern=None, view=None, view_for=None, permission=None, @@ -1308,12 +1307,6 @@ class Configurator(object): value if the ``pattern`` argument is ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` wins. - marker_pattern - - A dict of regular expression's that will be used in the place - of the default ``[^/]+`` regular expression for all replacement - markers in the route pattern. - xhr This value should be either ``True`` or ``False``. If this @@ -1536,8 +1529,7 @@ class Configurator(object): raise ConfigurationError('"pattern" argument may not be None') return mapper.connect(name, pattern, factory, predicates=predicates, - pregenerator=pregenerator, - marker_pattern=marker_pattern) + pregenerator=pregenerator) def get_routes_mapper(self): """ Return the :term:`routes mapper` object associated with diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py index f77ebbbb4..12c5cf220 100644 --- a/pyramid/tests/test_urldispatch.py +++ b/pyramid/tests/test_urldispatch.py @@ -218,9 +218,9 @@ class RoutesMapperTests(unittest.TestCase): self.assertEqual(mapper.generate('abc', {}), 123) class TestCompileRoute(unittest.TestCase): - def _callFUT(self, pattern, marker_pattern=None): + def _callFUT(self, pattern): from pyramid.urldispatch import _compile_route - return _compile_route(pattern, marker_pattern) + return _compile_route(pattern) def test_no_star(self): matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar') @@ -253,8 +253,7 @@ class TestCompileRoute(unittest.TestCase): self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00') def test_custom_regex(self): - matcher, generator = self._callFUT('foo/:baz/biz/:buz.:bar', - {'buz': '[^/\.]+'}) + matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}') self.assertEqual(matcher('/foo/baz/biz/buz.bar'), {'baz':'baz', 'buz':'buz', 'bar':'bar'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index 06ec647e5..0f8691a07 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -17,10 +17,10 @@ _marker = object() class Route(object): implements(IRoute) def __init__(self, name, pattern, factory=None, predicates=(), - pregenerator=None, marker_pattern=None): + pregenerator=None): self.pattern = pattern self.path = pattern # indefinite b/w compat, not in interface - self.match, self.generate = _compile_route(pattern, marker_pattern) + self.match, self.generate = _compile_route(pattern) self.name = name self.factory = factory self.predicates = predicates @@ -42,12 +42,11 @@ class RoutesMapper(object): return self.routes.get(name) def connect(self, name, pattern, factory=None, predicates=(), - pregenerator=None, marker_pattern=None): + pregenerator=None): if name in self.routes: oldroute = self.routes[name] self.routelist.remove(oldroute) - route = Route(name, pattern, factory, predicates, pregenerator, - marker_pattern) + route = Route(name, pattern, factory, predicates, pregenerator) self.routelist.append(route) self.routes[name] = route return route @@ -75,9 +74,16 @@ class RoutesMapper(object): return {'route':None, 'match':None} # stolen from bobo and modified -route_re = re.compile(r'(:[a-zA-Z]\w*)') -def _compile_route(route, marker_pattern=None): - marker_pattern = marker_pattern or {} +old_route_re = re.compile(r'(\:[a-zA-Z]\w*)') +route_re = re.compile(r'(\{[a-zA-Z][^\}]*\})') +def update_pattern(matchobj): + name = matchobj.group(0) + return '{%s}' % name[1:] + +def _compile_route(route): + if old_route_re.search(route) and not route_re.search(route): + route = old_route_re.sub(update_pattern, route) + if not route.startswith('/'): route = '/' + route star = None @@ -93,9 +99,13 @@ def _compile_route(route, marker_pattern=None): gen.append(prefix) while pat: name = pat.pop() - name = name[1:] + name = name[1:-1] + if ':' in name: + name, reg = name.split(':') + else: + reg = '[^/]+' gen.append('%%(%s)s' % name) - name = '(?P<%s>%s)' % (name, marker_pattern.get(name, '[^/]+')) + name = '(?P<%s>%s)' % (name, reg) rpat.append(name) s = pat.pop() if s: -- cgit v1.2.3 From 16c510efd5b05c503350e378beb94a9cd6a5de7d Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 20:48:58 -0800 Subject: Ensure the version changed info is visible. --- docs/narr/urldispatch.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 0d66f28a1..1e5d84060 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -95,7 +95,6 @@ registry`. Here's an example: config.add_route('myroute', '/prefix/{one}/{two}', view=myview) .. versionchanged:: 1.0a4 - Prior to 1.0a4, routes allow for a marker starting with a ``:``, for example:: -- cgit v1.2.3 From 0f89f91a5bf4f26734235d01e81dffc77abba9c4 Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 23:12:57 -0800 Subject: More colon references. --- docs/narr/urldispatch.rst | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 1e5d84060..ae0ecf629 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -96,12 +96,8 @@ registry`. Here's an example: .. versionchanged:: 1.0a4 Prior to 1.0a4, routes allow for a marker starting with a ``:``, for - example:: - - config.add_route('myroute', '/prefix/:one/:two', view=myview) - - Starting in 1.0a4, this style is deprecated in favor or ``{}`` usage - which allows for additional functionality. + example ``/prefix/{one}``. Starting in 1.0a4, this style is deprecated + in favor or ``{}`` usage which allows for additional functionality. .. index:: single: route configuration; view callable @@ -280,9 +276,9 @@ replacement marker. Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL ``/abc/``: -- ``/abc/:foo`` will not match. +- ``/abc/{foo}`` will not match. -- ``/:foo/`` will match. +- ``/{foo}/`` will match. Note that values representing path segments matched with a ``:segment`` match will be url-unquoted and decoded from UTF-8 into @@ -1246,7 +1242,7 @@ Such a ``factory`` might look like so: if article == '1': self.__acl__ = [ (Allow, 'editor', 'view') ] -If the route ``archives/:article`` is matched, and the article number +If the route ``archives/{article}`` is matched, and the article number is ``1``, :app:`Pyramid` will generate an ``Article`` :term:`context` with an ACL on it that allows the ``editor`` principal the ``view`` permission. Obviously you can do more generic things -- cgit v1.2.3 From 15c258849aa54eb99d272fbbef0e9c3d911d2cca Mon Sep 17 00:00:00 2001 From: Ben Bangert Date: Thu, 18 Nov 2010 23:25:14 -0800 Subject: There is no pattern_regexes. --- docs/narr/urldispatch.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index ae0ecf629..a86041e55 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -270,8 +270,8 @@ replacement marker ``:ext``. It is possible to use two replacement markers without any literal characters between them, for instance ``/{foo}{bar}``. This would be a nonsensical pattern -without specifying any ``pattern_regexes`` to restrict valid values of each -replacement marker. +without specifying a custom regular expression to restrict what a marker +captures. Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL ``/abc/``: -- cgit v1.2.3 From 60e316de2a685a1662006047ee89aca4f442e7ef Mon Sep 17 00:00:00 2001 From: William Chambers Date: Fri, 19 Nov 2010 19:15:29 -0500 Subject: Fix mako default filter to use unicode. This is mako's default. --- pyramid/mako_templating.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index e2330f3ad..6356e0db4 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -65,7 +65,7 @@ def renderer_factory(info): module_directory = settings.get('mako.module_directory') input_encoding = settings.get('mako.input_encoding', 'utf-8') error_handler = settings.get('mako.error_handler', None) - default_filters = settings.get('mako.default_filters', []) + default_filters = settings.get('mako.default_filters', None) imports = settings.get('mako.imports', []) if directories is None: raise ConfigurationError( -- cgit v1.2.3 From b5e5018c9b948929548615139533e9a8a504ff11 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 19 Nov 2010 21:13:48 -0500 Subject: gardening --- TODO.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.txt b/TODO.txt index fe411f749..3d36e3a3d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -52,6 +52,10 @@ Must-Have (before 1.0) - Better ``config.add_handler`` documentation. +- Fix DottedNameResolver to not convert ImportError to ConfigurationError if + the import that failed was unrelated to the import requested via a dotted + name. + Should-Have ----------- -- cgit v1.2.3 From 614f00c88733b5248922e2b610c96f7c8c3ff57c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 19 Nov 2010 22:28:50 -0500 Subject: - Remove calls to config.begin()/config.end() from startup config code in tutorials and paster templates (no longer required). --- CHANGES.txt | 10 +++++ TODO.txt | 5 +-- docs/narr/MyProject/myproject/__init__.py | 3 -- docs/narr/configuration.rst | 18 +++----- docs/narr/firstapp.rst | 49 ---------------------- docs/narr/project.rst | 8 ++-- docs/narr/unittesting.rst | 2 +- docs/tutorials/wiki/basiclayout.rst | 9 ++-- .../wiki/src/authorization/tutorial/__init__.py | 2 - .../wiki/src/basiclayout/tutorial/__init__.py | 2 - .../tutorials/wiki/src/models/tutorial/__init__.py | 2 - .../wiki/src/viewdecorators/tutorial/__init__.py | 2 - docs/tutorials/wiki/src/views/tutorial/__init__.py | 2 - docs/tutorials/wiki2/basiclayout.rst | 12 ++---- .../wiki2/src/authorization/tutorial/__init__.py | 2 - .../wiki2/src/basiclayout/tutorial/__init__.py | 2 - .../wiki2/src/models/tutorial/__init__.py | 2 - .../tutorials/wiki2/src/views/tutorial/__init__.py | 2 - .../alchemy/+package+/__init__.py_tmpl | 2 - .../pylons_basic/+package+/__init__.py_tmpl | 2 - .../pylons_minimal/+package+/__init__.py_tmpl | 2 - .../pylons_sqla/+package+/__init__.py_tmpl | 2 - .../routesalchemy/+package+/__init__.py_tmpl | 2 - .../starter/+package+/__init__.py_tmpl | 2 - .../starter_zcml/+package+/__init__.py_tmpl | 2 - .../zodb/+package+/__init__.py_tmpl | 2 - 26 files changed, 28 insertions(+), 122 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ed68770fd..692e3cc35 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,6 +26,16 @@ Features - Make test suite pass on PyPy (Chameleon doesn't work). +- Surrounding application configuration with ``config.begin()`` and + ``config.end()`` is no longer necessary. All paster templates have been + changed to no longer call these functions. + +Documentation +------------- + +- SQLAlchemy+URLDispatch and ZODB+Traversal tutorials have been updated to + not call ``config.begin()`` or ``config.end()``. + Bug Fixes --------- diff --git a/TODO.txt b/TODO.txt index 3d36e3a3d..ab2b521ae 100644 --- a/TODO.txt +++ b/TODO.txt @@ -16,9 +16,6 @@ Must-Have (before 1.0) - Use ``@register_view`` instead of ``@view_config`` and change view docs to use "view registration" instead of "view configuration". -- Remove calls to config.begin()/config.end() from startup config code in - tutorials and paster templates (no longer required). - - SQLAlchemy idiomatics: mcdonc: those paster templates all look pretty good... the @@ -59,7 +56,7 @@ Must-Have (before 1.0) Should-Have ----------- -- Try to make test suite pass on PyPy, IronPython. +- Try to make test suite pass on IronPython. - Add docs for httpexceptions module for each webob.exc class that inherits from WSGIHTTPException. diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/MyProject/myproject/__init__.py index bfbbfd4df..580dfe546 100644 --- a/docs/narr/MyProject/myproject/__init__.py +++ b/docs/narr/MyProject/myproject/__init__.py @@ -5,11 +5,8 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.add_view('myproject.views.my_view', context='myproject.models.MyModel', renderer='myproject:templates/mytemplate.pt') config.add_static_view('static', 'myproject:static') - config.end() return config.make_wsgi_app() - diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 6a91cbf75..ae02a5a6c 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -47,20 +47,16 @@ imperatively: if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') -We won't talk much about what this application does yet. Just note -that the "configuration' statements take place underneath the ``if -__name__ == '__main__':`` stanza in the form of method calls on a -:term:`Configurator` object (e.g. ``config.begin()``, -``config.add_view(...)``, and ``config.end()``. These statements take -place one after the other, and are executed in order, so the full -power of Python, including conditionals, can be employed in this mode -of configuration. +We won't talk much about what this application does yet. Just note that the +"configuration' statements take place underneath the ``if __name__ == +'__main__':`` stanza in the form of method calls on a :term:`Configurator` +object (e.g. ``config.add_view(...)``). These statements take place one +after the other, and are executed in order, so the full power of Python, +including conditionals, can be employed in this mode of configuration. .. index:: single: view_config @@ -123,9 +119,7 @@ and its subpackages. For example: if __name__ == '__main__': from pyramid.configuration import Configurator config = Configurator() - config.begin() config.scan() - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index bc21bf29f..9d3cad13c 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -38,10 +38,8 @@ configured imperatively: if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -149,10 +147,8 @@ imports and function definitions is placed within the confines of an if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) config.add_view(goodbye_world, name='goodbye') - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') @@ -190,29 +186,6 @@ this particular :app:`Pyramid` application. Methods called on the Configurator will cause registrations to be made in a :term:`application registry` associated with the application. -Beginning Configuration -~~~~~~~~~~~~~~~~~~~~~~~ - -.. ignore-next-block -.. code-block:: python - - config.begin() - -The :meth:`pyramid.configuration.Configurator.begin` method tells -the system that application configuration has begun. In particular, -this causes the :term:`application registry` associated with this -configurator to become the "current" application registry, meaning -that code which attempts to use the application registry :term:`thread -local` will obtain the registry associated with the configurator. -This is an explicit step because it's sometimes convenient to use a -configurator without causing the registry associated with the -configurator to become "current". - -.. note:: - - See :ref:`threadlocals_chapter` for a discussion about what it - means for an application registry to be "current". - .. _adding_configuration: Adding Configuration @@ -281,28 +254,6 @@ important. We can register ``goodbye_world`` first and ``hello_world`` second; :app:`Pyramid` will still give us the most specific callable when a request is dispatched to it. -Ending Configuration -~~~~~~~~~~~~~~~~~~~~ - -.. ignore-next-block -.. code-block:: python - - config.end() - -The :meth:`pyramid.configuration.Configurator.end` method tells the -system that application configuration has ended. It is the inverse of -:meth:`pyramid.configuration.Configurator.begin`. In particular, -this causes the :term:`application registry` associated with this -configurator to no longer be the "current" application registry, -meaning that code which attempts to use the application registry -:term:`thread local` will no longer obtain the registry associated -with the configurator. - -.. note:: - - See :ref:`threadlocals_chapter` for a discussion about what it - means for an application registry to be "current". - .. index:: single: make_wsgi_app single: WSGI application diff --git a/docs/narr/project.rst b/docs/narr/project.rst index f47e9f293..6e466b284 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -740,14 +740,14 @@ also informs Python that the directory which contains it is a *package*. #. Line 2 imports the ``get_root`` function from :mod:`myproject.models` that we use later. -#. Lines 4-14 define a function that returns a :app:`Pyramid` +#. Lines 4-12 define a function that returns a :app:`Pyramid` WSGI application. This function is meant to be called by the :term:`PasteDeploy` framework as a result of running ``paster serve``. Within this function, configuration is performed. - Lines 9-11 register a "default view" (a view that has no ``name`` + Lines 8-10 register a "default view" (a view that has no ``name`` attribute). It is registered so that it will be found when the :term:`context` of the request is an instance of the :class:`myproject.models.MyModel` class. The first argument to @@ -761,11 +761,11 @@ also informs Python that the directory which contains it is a *package*. ``templates`` directory of the ``myproject`` package. The template file it actually points to is a :term:`Chameleon` ZPT template file. - Line 12 registers a static view, which will serve up the files from the + Line 11 registers a static view, which will serve up the files from the ``mypackage:static`` :term:`resource specification` (the ``static`` directory of the ``mypackage`` package). - Line 14 returns a :term:`WSGI` application to the caller of the function + Line 12 returns a :term:`WSGI` application to the caller of the function (Paste). ``views.py`` diff --git a/docs/narr/unittesting.rst b/docs/narr/unittesting.rst index 5cd8a0683..26035726b 100644 --- a/docs/narr/unittesting.rst +++ b/docs/narr/unittesting.rst @@ -1,4 +1,4 @@ -.. index:: +\.. index:: single: unit testing single: integration testing diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index c05a53831..a94a1632d 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -48,14 +48,11 @@ entry point happens to be the ``app`` function within the file named factory` and the settings keywords parsed by PasteDeploy. The root factory is named ``get_root``. -#. *Lines 16-18*. Begin configuration using the ``begin`` method of - the :meth:`pyramid.configuration.Configurator` class, load the +#. *Line 16*. Load the ``configure.zcml`` file from our package using the - :meth:`pyramid.configuration.Configurator.load_zcml` method, and - end configuration using the - :meth:`pyramid.configuration.Configurator.end` method. + :meth:`pyramid.configuration.Configurator.load_zcml` method. -#. *Line 19*. Use the +#. *Line 17*. Use the :meth:`pyramid.configuration.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 124647ba9..a8a3c513e 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -16,7 +16,5 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml('configure.zcml') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index f6cf8b479..45e4d722b 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -13,8 +13,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml('configure.zcml') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index 7ef07e767..66e2f05ee 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -16,8 +16,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml('configure.zcml') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py index 7ef07e767..66e2f05ee 100644 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py @@ -16,8 +16,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml('configure.zcml') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index 7ef07e767..66e2f05ee 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -16,8 +16,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml('configure.zcml') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 9aca94fe5..9bcc17e97 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -52,10 +52,7 @@ entry point happens to be the ``app`` function within the file named deployment-related values such as ``reload_templates``, ``db_string``, etc. -#. *Line 15*. We call :meth:`pyramid.configuration.Configurator.begin` which - tells the configuration machinery we are starting configuration. - -#. *Line 16*. We call +#. *Line 15*. We call :meth:`pyramid.configuration.Configurator.add_static_view` with the arguments ``static`` (the name), and ``tutorial:static`` (the path). This registers a static resource view which will match any URL that starts with @@ -67,7 +64,7 @@ entry point happens to be the ``app`` function within the file named ``/static/foo``) will be used to compose a path to a static file resource, such as a CSS file. -#. *Lines 17-18*. Register a :term:`route configuration` via the +#. *Lines 16-17*. Register a :term:`route configuration` via the :meth:`pyramid.configuration.Configurator.add_route` method that will be used when the URL is ``/``. Since this route has an ``pattern`` equalling ``/`` it is the "default" route. The argument named ``view`` with the @@ -81,10 +78,7 @@ entry point happens to be the ``app`` function within the file named ``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer` will use this template to create a response. -#. *Line 19*. We call :meth:`pyramid.configuration.Configurator.end` which - tells the configuration machinery we are ending configuration. - -#. *Line 20*. We use the +#. *Line 18*. We use the :meth:`pyramid.configuration.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index 4bddea74c..8269617f2 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -22,7 +22,6 @@ def main(global_config, **settings): root_factory='tutorial.models.RootFactory', authentication_policy=authn_policy, authorization_policy=authz_policy) - config.begin() config.add_static_view('static', 'tutorial:static') config.add_route('view_wiki', '/', view='tutorial.views.view_wiki') config.add_route('login', '/login', @@ -44,6 +43,5 @@ def main(global_config, **settings): config.add_view('tutorial.login.login', renderer='tutorial:templates/login.pt', context='pyramid.exceptions.Forbidden') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py index 7a701fc02..0ae61fccd 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -12,11 +12,9 @@ def main(global_config, **settings): db_echo = settings.get('db_echo', 'false') initialize_sql(db_string, asbool(db_echo)) config = Configurator(settings=settings) - config.begin() config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.my_view', view_renderer='templates/mytemplate.pt') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 10fcd0cbc..6e291ffdf 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -12,9 +12,7 @@ def main(global_config, **settings): db_echo = settings.get('db_echo', 'false') initialize_sql(db_string, asbool(db_echo)) config = Configurator(settings=settings) - config.begin() config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.my_view', view_renderer='templates/mytemplate.pt') - config.end() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 9ef923e0b..947ce9b93 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -12,7 +12,6 @@ def main(global_config, **settings): db_echo = settings.get('db_echo', 'false') initialize_sql(db_string, asbool(db_echo)) config = Configurator(settings=settings) - config.begin() config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.view_wiki') config.add_route('view_page', '/:pagename', @@ -24,6 +23,5 @@ def main(global_config, **settings): config.add_route('edit_page', '/:pagename/edit_page', view='tutorial.views.edit_page', view_renderer='tutorial:templates/edit.pt') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl index 748f58692..c20e7dd15 100755 --- a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl @@ -12,7 +12,6 @@ def main(global_config, **settings): db_echo = settings.get('db_echo', 'false') get_root = appmaker(db_string, asbool(db_echo)) config = Configurator(settings=settings, root_factory=get_root) - config.begin() config.add_static_view('static', '{{package}}:static') config.add_view('{{package}}.views.view_root', context='{{package}}.models.MyApp', @@ -20,7 +19,6 @@ def main(global_config, **settings): config.add_view('{{package}}.views.view_model', context='{{package}}.models.MyModel', renderer="templates/model.pt") - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl index 6a8beb1a7..dd6edd3cf 100644 --- a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl @@ -5,7 +5,6 @@ def main(global_config, **settings): """ from pyramid.configuration import Configurator config = Configurator(settings=settings) - config.begin() session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') @@ -15,5 +14,4 @@ def main(global_config, **settings): action='index') config.add_subscriber('{{package}}.lib.subscribers.add_renderer_globals', 'pyramid.events.BeforeRender') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl index a1a353aae..57371eec9 100644 --- a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl @@ -5,7 +5,6 @@ def main(global_config, **settings): """ from pyramid.configuration import Configurator config = Configurator(settings=settings) - config.begin() session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') @@ -14,6 +13,5 @@ def main(global_config, **settings): action='index') config.add_subscriber('{{package}}.subscribers.add_renderer_globals', 'pyramid.events.BeforeRender') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl index a8df81fd6..0a4313976 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl @@ -13,7 +13,6 @@ def main(global_config, **settings): "configuration.") initialize_sql(db_string, asbool(settings.get('db_echo'))) config = Configurator(settings=settings) - config.begin() session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') @@ -22,5 +21,4 @@ def main(global_config, **settings): action='index') config.add_subscriber('{{package}}.subscribers.add_renderer_globals', 'pyramid.events.BeforeRender') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl index 8b13e3590..e68e0ed66 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl @@ -12,11 +12,9 @@ def main(global_config, **settings): db_echo = settings.get('db_echo', 'false') initialize_sql(db_string, asbool(db_echo)) config = Configurator(settings=settings) - config.begin() config.add_static_view('static', '{{package}}:static') config.add_route('home', '/', view='{{package}}.views.my_view', view_renderer='templates/mytemplate.pt') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/starter/+package+/__init__.py_tmpl b/pyramid/paster_templates/starter/+package+/__init__.py_tmpl index 5f170ad27..85cb7ec6a 100644 --- a/pyramid/paster_templates/starter/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/starter/+package+/__init__.py_tmpl @@ -5,11 +5,9 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.add_view('{{package}}.views.my_view', context='{{package}}.models.MyModel', renderer='{{package}}:templates/mytemplate.pt') config.add_static_view('static', '{{package}}:static') - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl b/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl index 19f35bee9..3a0c6b7de 100644 --- a/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/starter_zcml/+package+/__init__.py_tmpl @@ -6,8 +6,6 @@ def main(global_config, **settings): """ zcml_file = settings.get('configure_zcml', 'configure.zcml') config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml(zcml_file) - config.end() return config.make_wsgi_app() diff --git a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl index 4e879d40f..6b05ac7ad 100644 --- a/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/zodb/+package+/__init__.py_tmpl @@ -14,7 +14,5 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.begin() config.load_zcml(zcml_file) - config.end() return config.make_wsgi_app() -- cgit v1.2.3 From f765a6d0a32e8548f6188a792da88526c9115e98 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 19 Nov 2010 23:43:02 -0500 Subject: reprioritize --- TODO.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/TODO.txt b/TODO.txt index ab2b521ae..f1db6f758 100644 --- a/TODO.txt +++ b/TODO.txt @@ -35,18 +35,6 @@ Must-Have (before 1.0) the parameter names (e.g. 'url' instead of 'db_string') is still probably a good idea -- Non-bwcompat use of threadlocals that need to be documented or ameliorated: - - security.principals_allowed_by_permission - - resource.OverrideProvider._get_overrides: can't credibly be removed, - because it stores an overrideprovider as a module-scope global. - - traversal.traverse: this API is a stepchild, and needs to be changed. - - Configurator.add_translation_dirs: not passed any context but a message, - can't credibly be removed. - - Better ``config.add_handler`` documentation. - Fix DottedNameResolver to not convert ImportError to ConfigurationError if @@ -77,6 +65,18 @@ Should-Have Nice-to-Have ------------ +- Non-bwcompat use of threadlocals that need to be documented or ameliorated: + + security.principals_allowed_by_permission + + resource.OverrideProvider._get_overrides: can't credibly be removed, + because it stores an overrideprovider as a module-scope global. + + traversal.traverse: this API is a stepchild, and needs to be changed. + + Configurator.add_translation_dirs: not passed any context but a message, + can't credibly be removed. + - Supply ``X-Vhm-Host`` support. - Basic WSGI documentation (pipeline / app / server). -- cgit v1.2.3 From 12f38891a8c478787b06073d2d443827bbb95fb0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 20 Nov 2010 01:16:32 -0500 Subject: remove begin/end --- docs/designdefense.rst | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 92facf13c..e3a816269 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -40,9 +40,7 @@ Too Complex if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) - config.end() app = config.make_wsgi_app() serve(app) @@ -549,9 +547,7 @@ everything done completely imperatively. For example, the very most basic if __name__ == '__main__': config = Configurator() - config.begin() config.add_view(hello_world) - config.end() app = config.make_wsgi_app() serve(app) @@ -1674,37 +1670,6 @@ can interface with a WSGI application is placed on the server developer, not the web framework developer, making it more likely to be timely and correct. -:meth:`pyramid.configuration.Configurator.begin` and :meth:`pyramid.configuration.Configurator.end` methods -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The methods :meth:`pyramid.configuration.Configurator.begin` and -:meth:`pyramid.configuration.Configurator.end` are used to bracket -the configuration phase of a :app:`Pyramid` application. - -These exist because existing legacy third party *configuration* (not -runtime) code relies on a threadlocal stack being populated. The -``begin`` method pushes data on to a threadlocal stack. The ``end`` -method pops it back off. - -For the simplest applications, these lines are actually not required. -I *could* omit them from every Pyramid hello world app without ill -effect. However, when users use certain configuration methods (ones -not represented in the hello world app), calling code will begin to -fail when it is not bracketed between a ``begin()`` and an ``end()``. -It is just easier to tell users that this bracketing is required than -to try to explain to them which circumstances it is actually required -and which it is not, because the explanation is often torturous. - -The effectively-required execution of these two methods is a wholly -bogus artifact of an early bad design decision which encouraged -application developers to use threadlocal data structures during the -execution of configuration plugins. However, I don't hate my -framework's users enough to break backwards compatibility for the sake -of removing two boilerplate lines of code, so it stays, at least for -the foreseeable future. If I eventually figure out a way to remove -the requirement, these methods will turn into no-ops and they will be -removed from the documenation. - Wrapping Up +++++++++++ @@ -1724,9 +1689,7 @@ where comments take into account what we've discussed in the if __name__ == '__main__': from pyramid.configuration import Configurator config = Configurator() # no global application object. - config.begin() # bogus, but required. config.add_view(hello_world) # explicit non-decorator registration - config.end() # bogus, but required. app = config.make_wsgi_app() # explicitly WSGI serve(app, host='0.0.0.0') # explicitly WSGI -- cgit v1.2.3 From 35ce2adb609bfb3db346bc8cc937d13a0d2dddcd Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 20 Nov 2010 13:52:03 -0500 Subject: - Fix configurator to not convert ``ImportError`` to ``ConfigurationError`` if the import that failed was unrelated to the import requested via a dotted name when resolving dotted names (such as view dotted names). --- CHANGES.txt | 4 ++++ TODO.txt | 4 ---- pyramid/configuration.py | 12 ++++-------- pyramid/tests/test_configuration.py | 7 ++----- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 692e3cc35..aee0fae2f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,10 @@ Features ``config.end()`` is no longer necessary. All paster templates have been changed to no longer call these functions. +- Fix configurator to not convert ``ImportError`` to ``ConfigurationError`` + if the import that failed was unrelated to the import requested via a + dotted name when resolving dotted names (such as view dotted names). + Documentation ------------- diff --git a/TODO.txt b/TODO.txt index f1db6f758..e1692695c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -37,10 +37,6 @@ Must-Have (before 1.0) - Better ``config.add_handler`` documentation. -- Fix DottedNameResolver to not convert ImportError to ConfigurationError if - the import that failed was unrelated to the import requested via a dotted - name. - Should-Have ----------- diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 63d09efe3..c7505b9db 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -2844,14 +2844,10 @@ class DottedNameResolver(object): def maybe_resolve(self, dotted): if isinstance(dotted, basestring): - try: - if ':' in dotted: - return self._pkg_resources_style(dotted) - else: - return self._zope_dottedname_style(dotted) - except ImportError: - raise ConfigurationError( - 'The dotted name %r cannot be imported' % (dotted,)) + if ':' in dotted: + return self._pkg_resources_style(dotted) + else: + return self._zope_dottedname_style(dotted) return dotted diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index a8ea63f54..4c1182eab 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -216,9 +216,8 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result, pyramid.tests) def test_maybe_dotted_string_fail(self): - from pyramid.configuration import ConfigurationError config = self._makeOne() - self.assertRaises(ConfigurationError, + self.assertRaises(ImportError, config.maybe_dotted, 'cant.be.found') def test_maybe_dotted_notstring_success(self): @@ -4397,9 +4396,7 @@ class TestDottedNameResolver(unittest.TestCase): def test_resolve_missing_raises(self): typ = self._makeOne() - e = self.config_exc(typ.resolve, 'cant.be.found') - self.assertEqual(e.args[0], - "The dotted name 'cant.be.found' cannot be imported") + self.assertRaises(ImportError, typ.resolve, 'cant.be.found') def test_ctor_string_module_resolveable(self): import pyramid.tests -- cgit v1.2.3 From df3f64ac77304db5d95a1cd33f07320a458b278a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 20 Nov 2010 15:56:58 -0500 Subject: convert stray references to colon routing syntax to squiggly syntax --- docs/narr/contextfinding.rst | 2 +- docs/narr/declarative.rst | 2 +- docs/narr/handlers.rst | 10 ++++---- docs/narr/hybrid.rst | 30 +++++++++++----------- docs/tutorials/wiki2/definingviews.rst | 8 +++--- .../wiki2/src/authorization/tutorial/__init__.py | 6 ++--- .../wiki2/src/authorization/tutorial/tests.py | 8 +++--- .../tutorials/wiki2/src/views/tutorial/__init__.py | 6 ++--- docs/tutorials/wiki2/src/views/tutorial/tests.py | 8 +++--- docs/zcml/route.rst | 6 ++--- pyramid/configuration.py | 6 ++--- .../pylons_basic/+package+/__init__.py_tmpl | 2 +- .../pylons_minimal/+package+/__init__.py_tmpl | 2 +- .../pylons_sqla/+package+/__init__.py_tmpl | 2 +- pyramid/url.py | 4 +-- 15 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/narr/contextfinding.rst b/docs/narr/contextfinding.rst index c3fbe7f5a..770f97d15 100644 --- a/docs/narr/contextfinding.rst +++ b/docs/narr/contextfinding.rst @@ -75,7 +75,7 @@ URL dispatch can easily handle URLs such as ``http://example.com/members/Chris``, where it's assumed that each item "below" ``members`` in the URL represents a single member in some system. You just match everything "below" ``members`` to a particular -:term:`view callable`, e.g. ``/members/:memberid``. +:term:`view callable`, e.g. ``/members/{memberid}``. However, URL dispatch is not very convenient if you'd like your URLs to represent an arbitrary hierarchy. For example, if you need to diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst index 48a3ea134..b9dbcab7d 100644 --- a/docs/narr/declarative.rst +++ b/docs/narr/declarative.rst @@ -655,7 +655,7 @@ declaration` causes a route to be added to the application. diff --git a/docs/narr/handlers.rst b/docs/narr/handlers.rst index b8e7b5d9b..022f27115 100644 --- a/docs/narr/handlers.rst +++ b/docs/narr/handlers.rst @@ -59,11 +59,11 @@ be performed in order to register it with the system: .. code-block:: python - config.add_handler('hello', '/hello/:action', handler=Hello) + config.add_handler('hello', '/hello/{action}', handler=Hello) This example will result in a route being added for the pattern -``/hello/:action``, each method of the ``Hello`` class will then be examined -to register the views. The value of ``:action`` in the route pattern will be +``/hello/{action}``, each method of the ``Hello`` class will then be examined +to register the views. The value of ``{action}`` in the route pattern will be used to determine which view should be called, and each view in the class will be setup with a view predicate that requires a specific ``action`` name. @@ -98,7 +98,7 @@ For example: .. code-block:: python - config.add_handler('hello', '/hello/:action', + config.add_handler('hello', '/hello/{action}', handler='mypackage.handlers:MyHandler') In larger applications, it is advised to use a :term:`resource specification` @@ -219,7 +219,7 @@ Example: return {} # in the config - config.add_handler('hello', '/hello/:action', handler=Hello) + config.add_handler('hello', '/hello/{action}', handler=Hello) With this configuration, the url ``/hello/home`` will find a view configuration that results in calling the ``show_template`` method, then rendering the diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index b89d10c9f..e704463c7 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -42,8 +42,8 @@ configuration: # config is an instance of pyramid.configuration.Configurator - config.add_route('foobar', ':foo/:bar', view='myproject.views.foobar') - config.add_route('bazbuz', ':baz/:buz', view='myproject.views.bazbuz') + config.add_route('foobar', '{foo}/{bar}', view='myproject.views.foobar') + config.add_route('bazbuz', '{baz}/{buz}', view='myproject.views.bazbuz') Each :term:`route` typically corresponds to a single view callable, and when that route is matched during a request, the view callable @@ -185,7 +185,7 @@ of a route's pattern: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse') + config.add_route('home', '{foo}/{bar}/*traverse') A ``*traverse`` token at the end of the pattern in a route's configuration implies a "remainder" *capture* value. When it is used, @@ -243,7 +243,7 @@ route configuration statement: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse', + config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') The ``factory`` above points at the function we've defined. It @@ -267,14 +267,14 @@ to do. When the route configuration named ``home`` above is matched during a request, the matchdict generated will be based on its pattern: -``:foo/:bar/*traverse``. The "capture value" implied by the +``{foo}/{bar}/*traverse``. The "capture value" implied by the ``*traverse`` element in the pattern will be used to traverse the graph in order to find a context, starting from the root object returned from the root factory. In the above example, the :term:`root` object found will be the instance named ``root`` in ``routes.py``. -If the URL that matched a route with the pattern ``:foo/:bar/*traverse``, +If the URL that matched a route with the pattern ``{foo}/{bar}/*traverse``, is ``http://example.com/one/two/a/b/c``, the traversal path used against the root object will be ``a/b/c``. As a result, :app:`Pyramid` will attempt to traverse through the edges ``a``, @@ -296,7 +296,7 @@ invoked after a route matches: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse', + config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') config.add_view('mypackage.views.myview', route_name='home') @@ -326,7 +326,7 @@ when a hybrid route is matched: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse', + config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') config.add_view('mypackage.views.myview', name='home') config.add_view('mypackage.views.another_view', name='another', @@ -371,14 +371,14 @@ Here's a use of the ``traverse`` pattern in a call to .. code-block:: python :linenos: - config.add_route('abc', '/articles/:article/edit', - traverse='/articles/:article') + config.add_route('abc', '/articles/{article}/edit', + traverse='/articles/{article}') The syntax of the ``traverse`` argument is the same as it is for ``pattern``. -If, as above, the ``pattern`` provided is ``articles/:article/edit``, -and the ``traverse`` argument provided is ``/:article``, when a +If, as above, the ``pattern`` provided is ``articles/{article}/edit``, +and the ``traverse`` argument provided is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``1`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. @@ -467,7 +467,7 @@ startup time. .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse', + config.add_route('home', '{foo}/{bar}/*traverse', view='myproject.views.home') config.add_view('myproject.views.another', route_name='home') @@ -479,7 +479,7 @@ supply a view attribute. For example, this ``add_route`` call: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse', + config.add_route('home', '{foo}/{bar}/*traverse', view='myproject.views.home') Can also be spelled like so: @@ -487,7 +487,7 @@ Can also be spelled like so: .. code-block:: python :linenos: - config.add_route('home', ':foo/:bar/*traverse') + config.add_route('home', '{foo}/{bar}/*traverse') config.add_view('myproject.views.home', route_name='home') The two spellings are logically equivalent. In fact, the former is diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index b87cd6a64..0f446bb4e 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -24,7 +24,7 @@ The request passed to every view that is called as the result of a route match has an attribute named ``matchdict`` that contains the elements placed into the URL by the ``pattern`` of a ``route`` statement. For instance, if a call to :meth:`pyramid.configuration.Configurator.add_route` in -``__init__.py`` had the pattern ``:one/:two``, and the URL at +``__init__.py`` had the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar`` was invoked, matching this pattern, the matchdict dictionary attached to the request passed to the view would have a ``one`` key with the value ``foo`` and a ``two`` key with the value ``bar``. @@ -277,16 +277,16 @@ the order they're found in the ``__init__.py`` file. to the view named ``view_wiki`` in our ``views.py`` file with the name ``view_wiki``. This is the :term:`default view` for the wiki. -#. Add a declaration which maps the pattern ``/:pagename`` to the view named +#. Add a declaration which maps the pattern ``/{pagename}`` to the view named ``view_page`` in our ``views.py`` file with the view name ``view_page``. This is the regular view for a page. #. Add a declaration which maps the pattern - ``/add_page/:pagename`` to the view named ``add_page`` in our + ``/add_page/{pagename}`` to the view named ``add_page`` in our ``views.py`` file with the name ``add_page``. This is the add view for a new page. -#. Add a declaration which maps the pattern ``/:pagename/edit_page`` to the +#. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the view named ``edit_page`` in our ``views.py`` file with the name ``edit_page``. This is the edit view for a page. diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index 8269617f2..771b2e3d7 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -29,14 +29,14 @@ def main(global_config, **settings): view_renderer='tutorial:templates/login.pt') config.add_route('logout', '/logout', view='tutorial.login.logout') - config.add_route('view_page', '/:pagename', + config.add_route('view_page', '/{pagename}', view='tutorial.views.view_page', view_renderer='tutorial:templates/view.pt') - config.add_route('add_page', '/add_page/:pagename', + config.add_route('add_page', '/add_page/{pagename}', view='tutorial.views.add_page', view_renderer='tutorial:templates/edit.pt', view_permission='edit') - config.add_route('edit_page', '/:pagename/edit_page', + config.add_route('edit_page', '/{pagename}/edit_page', view='tutorial.views.edit_page', view_renderer='tutorial:templates/edit.pt', view_permission='edit') diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py index 65330ce17..c78899797 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py @@ -14,9 +14,9 @@ def _initTestingDB(): return DBSession def _registerRoutes(config): - config.add_route('view_page', ':pagename') - config.add_route('edit_page', ':pagename/edit_page') - config.add_route('add_page', 'add_page/:pagename') + config.add_route('view_page', '{pagename}') + config.add_route('edit_page', '{pagename}/edit_page') + config.add_route('add_page', 'add_page/{pagename}') class ViewWikiTests(unittest.TestCase): def setUp(self): @@ -28,7 +28,7 @@ class ViewWikiTests(unittest.TestCase): def test_it(self): from tutorial.views import view_wiki - self.config.add_route('view_page', ':pagename') + self.config.add_route('view_page', '{pagename}') request = testing.DummyRequest() response = view_wiki(request) self.assertEqual(response.location, 'http://example.com/FrontPage') diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 947ce9b93..aa75c359a 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -14,13 +14,13 @@ def main(global_config, **settings): config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.view_wiki') - config.add_route('view_page', '/:pagename', + config.add_route('view_page', '/{pagename}', view='tutorial.views.view_page', view_renderer='tutorial:templates/view.pt') - config.add_route('add_page', '/add_page/:pagename', + config.add_route('add_page', '/add_page/{pagename}', view='tutorial.views.add_page', view_renderer='tutorial:templates/edit.pt') - config.add_route('edit_page', '/:pagename/edit_page', + config.add_route('edit_page', '/{pagename}/edit_page', view='tutorial.views.edit_page', view_renderer='tutorial:templates/edit.pt') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py index 40336fca4..435e4b588 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py @@ -14,9 +14,9 @@ def _initTestingDB(): return DBSession def _registerRoutes(config): - config.add_route('view_page', ':pagename') - config.add_route('edit_page', ':pagename/edit_page') - config.add_route('add_page', 'add_page/:pagename') + config.add_route('view_page', '{pagename}') + config.add_route('edit_page', '{pagename}/edit_page') + config.add_route('add_page', 'add_page/{pagename}') class ViewWikiTests(unittest.TestCase): def setUp(self): @@ -28,7 +28,7 @@ class ViewWikiTests(unittest.TestCase): def test_it(self): from tutorial.views import view_wiki - self.config.add_route('view_page', ':pagename') + self.config.add_route('view_page', '{pagename}') request = testing.DummyRequest() response = view_wiki(request) self.assertEqual(response.location, 'http://example.com/FrontPage') diff --git a/docs/zcml/route.rst b/docs/zcml/route.rst index ed849e3c1..c3bec72df 100644 --- a/docs/zcml/route.rst +++ b/docs/zcml/route.rst @@ -10,7 +10,7 @@ Attributes ~~~~~~~~~~ ``pattern`` - The pattern of the route e.g. ``ideas/:idea``. This attribute is + The pattern of the route e.g. ``ideas/{idea}``. This attribute is required. See :ref:`route_pattern_syntax` for information about the syntax of route patterns. @@ -51,9 +51,9 @@ Attributes The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided to the - ``route`` directive is ``articles/:article/edit``, and the + ``route`` directive is ``articles/{article}/edit``, and the ``traverse`` argument provided to the ``route`` directive is - ``/:article``, when a request comes in that causes the route to + ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is '1' (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's diff --git a/pyramid/configuration.py b/pyramid/configuration.py index c7505b9db..4c3e8b04b 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -1260,9 +1260,9 @@ class Configurator(object): The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided to - ``add_route`` is ``articles/:article/edit``, and the + ``add_route`` is ``articles/{article}/edit``, and the ``traverse`` argument provided to ``add_route`` is - ``/:article``, when a request comes in that causes the route + ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is '1' (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that @@ -1306,7 +1306,7 @@ class Configurator(object): pattern - The pattern of the route e.g. ``ideas/:idea``. This + The pattern of the route e.g. ``ideas/{idea}``. This argument is required. See :ref:`route_path_pattern_syntax` for information about the syntax of route patterns. If the pattern doesn't match the current URL, route matching diff --git a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl index dd6edd3cf..9f3793538 100644 --- a/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_basic/+package+/__init__.py_tmpl @@ -8,7 +8,7 @@ def main(global_config, **settings): session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') - config.add_handler('action', '/:action', + config.add_handler('action', '/{action}', '{{package}}.handlers.hello:HelloHandler') config.add_handler('home', '/', '{{package}}.handlers.hello:HelloHandler', action='index') diff --git a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl index 57371eec9..5e284ec88 100644 --- a/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_minimal/+package+/__init__.py_tmpl @@ -8,7 +8,7 @@ def main(global_config, **settings): session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') - config.add_handler('action', '/:action', '{{package}}.handlers:MyHandler') + config.add_handler('action', '/{action}', '{{package}}.handlers:MyHandler') config.add_handler('home', '/', '{{package}}.handlers:MyHandler', action='index') config.add_subscriber('{{package}}.subscribers.add_renderer_globals', diff --git a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl index 0a4313976..fc06c9908 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl @@ -16,7 +16,7 @@ def main(global_config, **settings): session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) config.add_static_view('static', '{{package}}:static/') - config.add_handler('main', '/:action', '{{package}}.handlers:MyHandler') + config.add_handler('main', '/{action}', '{{package}}.handlers:MyHandler') config.add_handler('home', '/', '{{package}}.handlers:MyHandler', action='index') config.add_subscriber('{{package}}.subscribers.add_renderer_globals', diff --git a/pyramid/url.py b/pyramid/url.py index 76d95689c..2e73e9cf5 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -32,7 +32,7 @@ def route_url(route_name, request, *elements, **kw): enough arguments, for example). For example, if you've defined a route named "foobar" with the path - ``:foo/:bar/*traverse``:: + ``:foo/{bar}/*traverse``:: route_url('foobar', request, foo='1') => route_url('foobar', request, foo='1', bar='2') => @@ -166,7 +166,7 @@ def route_path(route_name, request, *elements, **kw): and anchor data are present in the returned string. For example, if you've defined a route named 'foobar' with the path - ``/:foo/:bar``, this call to ``route_path``:: + ``/{foo}/{bar}``, this call to ``route_path``:: route_path('foobar', request, foo='1', bar='2') -- cgit v1.2.3 From 94a527194b22fdd11263d94bf7be19f524a0141e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 20 Nov 2010 16:35:40 -0500 Subject: gardening, add twophase todo --- TODO.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index e1692695c..479b20c3a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,7 +6,8 @@ Must-Have (before 1.0) - Add a ``handler`` ZCML directive. This implies some slightly dicey refactoring of the configurator to allow it to generate ZCML - "discriminators" for views and routes. + "discriminators" for views and routes, that could be implemented in terms + of "twophase configuration" in "should have" below. - Provide a .flash API on session object. @@ -40,8 +41,6 @@ Must-Have (before 1.0) Should-Have ----------- -- Try to make test suite pass on IronPython. - - Add docs for httpexceptions module for each webob.exc class that inherits from WSGIHTTPException. @@ -58,9 +57,13 @@ Should-Have - Change "Cleaning up After a Request" in the urldispatch chapter to use ``request.add_response_callback``. +- Twophase configuration (config = Configurator(autocommit=False)) + Nice-to-Have ------------ +- Try to make test suite pass on IronPython. + - Non-bwcompat use of threadlocals that need to be documented or ameliorated: security.principals_allowed_by_permission -- cgit v1.2.3 From 7eb4adc19acd98c10fa7d5f4f63b6ed1fe82b234 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 00:37:42 -0500 Subject: prep for 1.0a4 --- CHANGES.txt | 15 ++++++++------- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aee0fae2f..84b103e65 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.0a4 (2010-11-21) +================== Features -------- @@ -8,10 +8,11 @@ Features in the pattern, instead of immediately following a ``/``. - URL Dispatch now uses the form ``{marker}`` to denote a replace marker in - the route pattern instead of ``:marker``. The old syntax is still backwards - compatible and accepted. The new format allows a regular expression for that - marker location to be used instead of the default ``[^/]+``, for example - ``{marker:\d+}`` is now valid to require the marker to be digits. + the route pattern instead of ``:marker``. The old colon-style marker syntax + is still accepted for backwards compatibility. The new format allows a + regular expression for that marker location to be used instead of the + default ``[^/]+``, for example ``{marker:\d+}`` is now valid to require the + marker to be digits. - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling @@ -62,7 +63,7 @@ Bug Fixes ASCII string rather than being passed along to downstream code as a convenience to the user and to prevent puzzling second-order failures from cropping up (all failures will occur within ``pyramid.traversal.traverse`` - rather than later down the line as the result of calling + rather than later down the line as the result of calling e.g. ``traversal_path``). Backwards Incompatibilities diff --git a/docs/conf.py b/docs/conf.py index 81096da3b..e520c9d82 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -76,7 +76,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.0a3' +version = '1.0a4' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index c1939f73c..29f4ddce6 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ############################################################################## -__version__ = '0.0' +__version__ = '1.0a4' import os import platform -- cgit v1.2.3 From 406259adbe413b91d2629471d0d8be584f0abd7c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 00:39:22 -0500 Subject: note bfg qualification to version numer --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 84b103e65..0f74b1616 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -70,7 +70,7 @@ Backwards Incompatibilities --------------------------- - The ``pyramid.testing.zcml_configure`` API has been removed. It had been - advertised as removed since 1.2a1, but hadn't actually been. + advertised as removed since repoze.bfg 1.2a1, but hadn't actually been. Deprecations ------------ -- cgit v1.2.3 From aedc211bd5a9b9bef4aae8a3b3f0986fe4263463 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 00:44:56 -0500 Subject: back to development --- CHANGES.txt | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0f74b1616..8540a518c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +Next release +============ + + 1.0a4 (2010-11-21) ================== diff --git a/setup.py b/setup.py index 29f4ddce6..c1939f73c 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ############################################################################## -__version__ = '1.0a4' +__version__ = '0.0' import os import platform -- cgit v1.2.3 From 2197ff06b492b7d1acf29df426c0fccd6d5dc98e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 02:05:50 -0500 Subject: - "Sample Applications" section of docs changed to note existence of Cluegun, Shootout and Virginia sample applications, ported from their repoze.bfg origin packages. --- CHANGES.txt | 7 +++++++ docs/index.rst | 51 ++++++++++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8540a518c..33e87a7c8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Next release ============ +Documentation +------------- + +- "Sample Applications" section of docs changed to note existence of Cluegun, + Shootout and Virginia sample applications, ported from their repoze.bfg + origin packages. + 1.0a4 (2010-11-21) ================== diff --git a/docs/index.rst b/docs/index.rst index bfe956af2..a1b7f7553 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -113,38 +113,39 @@ Design Documentation Sample Applications =================== -.. warning:: +`cluegun `_ is a simple pastebin +application based on Rocky Burt's `ClueBin +`_. It demonstrates form +processing, security, and the use of :term:`ZODB` within a :mod:`repoze.bfg` +application. It also has very simple :term:`repoze.who` integration. Check +this application out via:: - These applications are for an older version of :app:`Pyramid`, - which was named :mod:`repoze.bfg`. We'll be updating them soon to - use :app:`Pyramid`. + git clone git://github.com/Pylons/cluegun.git -`repoze.cluegun `_ is a -simple pastebin application based on Rocky Burt's `ClueBin -`_. It demonstrates form -processing, security, and the use of :term:`ZODB` within a -:mod:`repoze.bfg` application. It also has very simple -:term:`repoze.who` integration. Check this application out of -Subversion via:: +`virginia `_ is a very simple dynamic +file rendering application. It is willing to render structured text +documents, HTML documents, and images from a filesystem directory. An +earlier version of this application runs the `repoze.org +`_ website. Check this application out via:: - svn co http://svn.repoze.org/repoze.cluegun/trunk repoze.cluegun + git clone git://github.com/Pylons/virginia.git -`repoze.virginia `_ is a -very simple dynamic file rendering application. It is willing to -render structured text documents, HTML documents, and images from a -filesystem directory. This application runs the `repoze.org -`_ website. Check this application out of -Subversion via:: +`shootout `_ is an example "idea +competition" application by Carlos de la Guardia. It demonstrates a hybrid +of :term:`URL dispatch` and :term:`traversal` and integration with +`SQLAlchemy `_ and :term:`repoze.who`. Check +this application out of version control via:: - svn co http://svn.repoze.org/repoze.virginia/trunk repoze.virginia + git clone git://github.com/Pylons/shootout.git -`repoze.shootout `_ is -an example "idea competition" application by Carlos de la Guardia. It -demonstrates a hybrid of :term:`URL dispatch` and :term:`traversal` -and integration with `SQLAlchemy `_ and -:term:`repoze.who`. Check this application out of Subversion via:: +Older Sample Applications (repoze.bfg) +====================================== + +.. note:: - svn co http://svn.repoze.org/repoze.shootout/trunk repoze.shootout + These applications are for an older version of :app:`Pyramid`, which was + named :mod:`repoze.bfg`. They won't work unmodified under Pyramid, but + might provide useful clues. `bfgsite `_ is the software which runs the `bfg.repoze.org `_ website. It -- cgit v1.2.3 From b44b9302f7a89de5444177137a3c915756d1ce0b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 13:36:07 -0500 Subject: - The ``pyramid_routesalchemy`` paster template's unit tests failed (``AssertionError: 'SomeProject' != 'someproject'``). This is fixed. --- CHANGES.txt | 6 ++++++ pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 33e87a7c8..36b610e10 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,12 @@ Next release ============ +Bug Fixes +--------- + +- The ``pyramid_routesalchemy`` paster template's unit tests failed + (``AssertionError: 'SomeProject' != 'someproject'``). This is fixed. + Documentation ------------- diff --git a/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl index de75e5df0..142341333 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl @@ -21,4 +21,4 @@ class TestMyView(unittest.TestCase): request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['root'].name, 'root') - self.assertEqual(info['project'], '{{package}}') + self.assertEqual(info['project'], '{{project}}') -- cgit v1.2.3 From 8a444e2013d4d97cc718119c7456ed8d32e7521d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 13:37:09 -0500 Subject: gardening --- TODO.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 479b20c3a..0ed31a312 100644 --- a/TODO.txt +++ b/TODO.txt @@ -84,7 +84,8 @@ Nice-to-Have - Change docs about creating a venusian decorator to not use ZCA. -- ``decorator=`` parameter to bfg_view. +- ``decorator=`` parameter to view_config. This would replace the existing + _map_view "decorator" if it existed. - Try to better explain the relationship between a renderer and a template in the templates chapter and elsewhere. Scan the -- cgit v1.2.3 From 12423894aa3facd9b6daa556cbc8cd8a5213c788 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 16:13:27 -0500 Subject: bfg -> pyramid --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index a1b7f7553..96cdc647b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -116,7 +116,7 @@ Sample Applications `cluegun `_ is a simple pastebin application based on Rocky Burt's `ClueBin `_. It demonstrates form -processing, security, and the use of :term:`ZODB` within a :mod:`repoze.bfg` +processing, security, and the use of :term:`ZODB` within a :app:`Pyramid` application. It also has very simple :term:`repoze.who` integration. Check this application out via:: -- cgit v1.2.3 From 957f614be664cb3b2015c39e2ac3441b7fda2f1b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 17:08:02 -0500 Subject: - Add logging configuration to all paster templates. - ``pyramid_alchemy``, ``pyramid_routesalchemy``, and ``pylons_sqla`` paster templates now use idiomatic SQLAlchemy configuration in their respective ``.ini`` files and Python code. --- CHANGES.txt | 9 +++++ docs/narr/MyProject/development.ini | 26 +++++++++++++ .../alchemy/+package+/__init__.py_tmpl | 8 ++-- .../paster_templates/alchemy/+package+/models.py | 7 ++-- .../paster_templates/alchemy/development.ini_tmpl | 37 ++++++++++++++++++- .../pylons_basic/development.ini_tmpl | 31 +++++++++++++++- .../pylons_minimal/development.ini_tmpl | 26 +++++++++++++ .../pylons_sqla/+package+/__init__.py_tmpl | 12 +++--- .../pylons_sqla/+package+/models.py | 3 +- .../pylons_sqla/development.ini_tmpl | 39 ++++++++++++++++++-- .../routesalchemy/+package+/__init__.py_tmpl | 8 ++-- .../routesalchemy/+package+/models.py | 4 +- .../routesalchemy/development.ini_tmpl | 43 +++++++++++++++++++--- .../paster_templates/starter/development.ini_tmpl | 26 +++++++++++++ .../starter_zcml/development.ini_tmpl | 26 +++++++++++++ pyramid/paster_templates/zodb/development.ini_tmpl | 26 +++++++++++++ 16 files changed, 294 insertions(+), 37 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 36b610e10..a4481edb3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,15 @@ Next release ============ +Features +-------- + +- Add logging configuration to all paster templates. + +- ``pyramid_alchemy``, ``pyramid_routesalchemy``, and ``pylons_sqla`` paster + templates now use idiomatic SQLAlchemy configuration in their respective + ``.ini`` files and Python code. + Bug Fixes --------- diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index 9c51cfe6e..80d89e46a 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -15,3 +15,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl index c20e7dd15..aabbce27b 100755 --- a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl @@ -1,16 +1,14 @@ from pyramid.configuration import Configurator from pyramid.settings import asbool +from sqlalchemy import engine_from_config from {{package}}.models import appmaker def main(global_config, **settings): """ This function returns a WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - get_root = appmaker(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + get_root = appmaker(engine) config = Configurator(settings=settings, root_factory=get_root) config.add_static_view('static', '{{package}}:static') config.add_view('{{package}}.views.view_root', diff --git a/pyramid/paster_templates/alchemy/+package+/models.py b/pyramid/paster_templates/alchemy/+package+/models.py index 336613cf9..1134cce07 100755 --- a/pyramid/paster_templates/alchemy/+package+/models.py +++ b/pyramid/paster_templates/alchemy/+package+/models.py @@ -73,8 +73,7 @@ def populate(): session.flush() transaction.commit() -def initialize_sql(db_string, db_echo=False): - engine = create_engine(db_string, echo=db_echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) @@ -83,6 +82,6 @@ def initialize_sql(db_string, db_echo=False): except IntegrityError: pass -def appmaker(db_string, db_echo=False): - initialize_sql(db_string, db_echo) +def appmaker(engine): + initialize_sql(engine) return default_get_root diff --git a/pyramid/paster_templates/alchemy/development.ini_tmpl b/pyramid/paster_templates/alchemy/development.ini_tmpl index de4605e33..4f4b98ac0 100644 --- a/pyramid/paster_templates/alchemy/development.ini_tmpl +++ b/pyramid/paster_templates/alchemy/development.ini_tmpl @@ -5,8 +5,7 @@ debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/{{package}}.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] pipeline = @@ -18,3 +17,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/pylons_basic/development.ini_tmpl b/pyramid/paster_templates/pylons_basic/development.ini_tmpl index 569256739..b14ecc7e2 100644 --- a/pyramid/paster_templates/pylons_basic/development.ini_tmpl +++ b/pyramid/paster_templates/pylons_basic/development.ini_tmpl @@ -13,10 +13,37 @@ session.key = {{project}} session.secret = {{random_string}} [pipeline:main] -pipeline = egg:WebError#evalerror - {{project}} +pipeline = + egg:WebError#evalerror + {{project}} [server:main] use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/pylons_minimal/development.ini_tmpl b/pyramid/paster_templates/pylons_minimal/development.ini_tmpl index 569256739..d31cd1ca2 100644 --- a/pyramid/paster_templates/pylons_minimal/development.ini_tmpl +++ b/pyramid/paster_templates/pylons_minimal/development.ini_tmpl @@ -20,3 +20,29 @@ pipeline = egg:WebError#evalerror use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl index fc06c9908..867dd8b3e 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl @@ -3,15 +3,15 @@ from pyramid.settings import asbool from pyramid_beaker import session_factory_from_settings +from sqlalchemy import engine_from_config + +from {{package}}.models import initialize_sql + def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - from {{package}}.models import initialize_sql - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application " - "configuration.") - initialize_sql(db_string, asbool(settings.get('db_echo'))) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) config = Configurator(settings=settings) session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory) diff --git a/pyramid/paster_templates/pylons_sqla/+package+/models.py b/pyramid/paster_templates/pylons_sqla/+package+/models.py index 092166902..6418f0c8b 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/models.py +++ b/pyramid/paster_templates/pylons_sqla/+package+/models.py @@ -36,8 +36,7 @@ def populate(): DBSession.flush() transaction.commit() -def initialize_sql(db_string, db_echo=False): - engine = create_engine(db_string, echo=db_echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/pyramid/paster_templates/pylons_sqla/development.ini_tmpl b/pyramid/paster_templates/pylons_sqla/development.ini_tmpl index 936d41b29..1cc1a42e4 100644 --- a/pyramid/paster_templates/pylons_sqla/development.ini_tmpl +++ b/pyramid/paster_templates/pylons_sqla/development.ini_tmpl @@ -1,13 +1,12 @@ [app:{{project}}] use = egg:{{project}} reload_templates = true -mako.directories = {{package}}:templates debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/tutorial.db -db_echo = true +mako.directories = {{package}}:templates +sqlalchemy.url = sqlite:///%(here)s/{{project}}.db session.type = file session.data_dir = %(here)s/data/sessions/data session.lock_dir = %(here)s/data/sessions/lock @@ -24,3 +23,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl index e68e0ed66..f5ce7d77b 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl @@ -1,16 +1,14 @@ from pyramid.configuration import Configurator from pyramid.settings import asbool +from sqlalchemy import engine_from_config from {{package}}.models import initialize_sql def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - initialize_sql(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', '{{package}}:static') config.add_route('home', '/', view='{{package}}.views.my_view', diff --git a/pyramid/paster_templates/routesalchemy/+package+/models.py b/pyramid/paster_templates/routesalchemy/+package+/models.py index a1726ebf4..9da906752 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/models.py +++ b/pyramid/paster_templates/routesalchemy/+package+/models.py @@ -1,6 +1,5 @@ import transaction -from sqlalchemy import create_engine from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import Unicode @@ -33,8 +32,7 @@ def populate(): session.flush() transaction.commit() -def initialize_sql(db_string, db_echo=False): - engine = create_engine(db_string, echo=db_echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/pyramid/paster_templates/routesalchemy/development.ini_tmpl b/pyramid/paster_templates/routesalchemy/development.ini_tmpl index de4605e33..a1cbff75f 100644 --- a/pyramid/paster_templates/routesalchemy/development.ini_tmpl +++ b/pyramid/paster_templates/routesalchemy/development.ini_tmpl @@ -1,20 +1,53 @@ -[app:{{package}}] -use = egg:{{package}} +[app:{{project}}] +use = egg:{{project}} reload_templates = true debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/{{package}}.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] pipeline = egg:WebError#evalerror egg:repoze.tm2#tm - {{package}} + {{project}} [server:main] use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/starter/development.ini_tmpl b/pyramid/paster_templates/starter/development.ini_tmpl index 5031742db..328422ef8 100644 --- a/pyramid/paster_templates/starter/development.ini_tmpl +++ b/pyramid/paster_templates/starter/development.ini_tmpl @@ -15,3 +15,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/starter_zcml/development.ini_tmpl b/pyramid/paster_templates/starter_zcml/development.ini_tmpl index 5031742db..328422ef8 100644 --- a/pyramid/paster_templates/starter_zcml/development.ini_tmpl +++ b/pyramid/paster_templates/starter_zcml/development.ini_tmpl @@ -15,3 +15,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid/paster_templates/zodb/development.ini_tmpl b/pyramid/paster_templates/zodb/development.ini_tmpl index 85a0681b7..671c8b069 100644 --- a/pyramid/paster_templates/zodb/development.ini_tmpl +++ b/pyramid/paster_templates/zodb/development.ini_tmpl @@ -18,3 +18,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration -- cgit v1.2.3 From d0e2f661e07d43188435b25aec0577dfbd50cfb0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 17:46:14 -0500 Subject: - SQLAlchemy+URLDispatch tutorial updated to integrate changes to ``pyramid_routesalchemy`` template. --- .gitignore | 1 + CHANGES.txt | 3 ++ docs/tutorials/wiki2/basiclayout.rst | 41 +++++++++------------- .../wiki2/src/authorization/development.ini | 37 +++++++++++++++++-- .../wiki2/src/authorization/tutorial/__init__.py | 9 ++--- .../wiki2/src/authorization/tutorial/models.py | 4 +-- .../wiki2/src/basiclayout/development.ini | 37 +++++++++++++++++-- .../wiki2/src/basiclayout/tutorial/__init__.py | 9 ++--- .../wiki2/src/basiclayout/tutorial/models.py | 4 +-- .../wiki2/src/basiclayout/tutorial/tests.py | 3 +- docs/tutorials/wiki2/src/models/development.ini | 37 +++++++++++++++++-- .../wiki2/src/models/tutorial/__init__.py | 9 ++--- docs/tutorials/wiki2/src/models/tutorial/models.py | 4 +-- docs/tutorials/wiki2/src/views/development.ini | 37 +++++++++++++++++-- .../tutorials/wiki2/src/views/tutorial/__init__.py | 9 ++--- docs/tutorials/wiki2/src/views/tutorial/models.py | 4 +-- .../alchemy/+package+/__init__.py_tmpl | 1 - .../pylons_sqla/+package+/__init__.py_tmpl | 1 - .../pylons_sqla/+package+/tests.py_tmpl | 3 +- .../routesalchemy/+package+/__init__.py_tmpl | 1 - .../routesalchemy/+package+/tests.py_tmpl | 3 +- 21 files changed, 183 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 706f6493d..ae0b17b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.pt.py *.txt.py .coverage +tutorial.db env26/ env24/ env27/ diff --git a/CHANGES.txt b/CHANGES.txt index a4481edb3..9fa66ce1f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,6 +23,9 @@ Documentation Shootout and Virginia sample applications, ported from their repoze.bfg origin packages. +- SQLAlchemy+URLDispatch tutorial updated to integrate changes to + ``pyramid_routesalchemy`` template. + 1.0a4 (2010-11-21) ================== diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 9bcc17e97..01fdd351e 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -32,27 +32,21 @@ entry point happens to be the ``app`` function within the file named #. *Lines 1-4*. Imports to support later code. -#. *Lines 9-11*. Get the database configuration string from the - ``development.ini`` file's ``[app:sqlalchemy]`` section. This will be a - URI (something like ``sqlite://``). +#. *Line 9*. Create a SQLAlchemy database engine from the ``sqlalchemy.`` + prefixed settings in the ``development.ini`` file's ``[app:tutorial]`` + section. This will be a URI (something like ``sqlite://``). -#. *Line 12*. Get the database echo setting from ``development.ini`` - file's ``[app:sqlalchemy]`` section. This will either be ``true`` - or ``false``. If ``true``, the application will print SQL to the - console as it is generated and run by SQLAlchemy. By default, it - is false. +#. *Line 10*. We initialize our SQL database using SQLAlchemy, passing + it the engine -#. Line *13*. We initialize our SQL database using SQLAlchemy, passing - it the db string and a variant of the db_echo value. - -#. *Line 14*. We construct a :term:`Configurator`. ``settings`` is +#. *Line 11*. We construct a :term:`Configurator`. ``settings`` is passed as a keyword argument with the dictionary values passed by PasteDeploy as the ``settings`` argument. This will be a dictionary of settings parsed by PasteDeploy, which contains deployment-related values such as ``reload_templates``, ``db_string``, etc. -#. *Line 15*. We call +#. *Line 12*. We call :meth:`pyramid.configuration.Configurator.add_static_view` with the arguments ``static`` (the name), and ``tutorial:static`` (the path). This registers a static resource view which will match any URL that starts with @@ -64,7 +58,7 @@ entry point happens to be the ``app`` function within the file named ``/static/foo``) will be used to compose a path to a static file resource, such as a CSS file. -#. *Lines 16-17*. Register a :term:`route configuration` via the +#. *Lines 13-14*. Register a :term:`route configuration` via the :meth:`pyramid.configuration.Configurator.add_route` method that will be used when the URL is ``/``. Since this route has an ``pattern`` equalling ``/`` it is the "default" route. The argument named ``view`` with the @@ -78,7 +72,7 @@ entry point happens to be the ``app`` function within the file named ``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer` will use this template to create a response. -#. *Line 18*. We use the +#. *Line 15*. We use the :meth:`pyramid.configuration.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. @@ -97,29 +91,28 @@ Here is the source for ``models.py``: :linenos: :language: py -#. *Lines 1-14*. Imports to support later code. +#. *Lines 1-13*. Imports to support later code. -#. *Line 16*. We set up a SQLAlchemy "DBSession" object here. We +#. *Line 15*. We set up a SQLAlchemy "DBSession" object here. We specify that we'd like to use the "ZopeTransactionExtension". This extension is an extension which allows us to use a *transaction manager* instead of controlling commits and aborts to database operations by hand. -#. *Line 17*. We create a declarative ``Base`` object to use as a +#. *Line 16*. We create a declarative ``Base`` object to use as a base class for our model. -#. *Lines 19-27*. A model class named ``MyModel``. It has an +#. *Lines 18-26*. A model class named ``MyModel``. It has an ``__init__`` that takes a two arguments (``name``, and ``value``). It stores these values as ``self.name`` and ``self.value`` within the ``__init__`` function itself. The ``MyModel`` class also has a ``__tablename__`` attribute. This informs SQLAlchemy which table to use to store the data representing instances of this class. -#. *Lines 29-34*. A function named ``populate`` which adds a single +#. *Lines 28-33*. A function named ``populate`` which adds a single model instance into our SQL storage and commits a transaction. -#. *Lines 36-44*. A function named ``initialize_sql`` which sets up - an actual SQL database and binds it to our SQLAlchemy DBSession - object. It also calls the ``populate`` function, to do initial - database population. +#. *Lines 35-42*. A function named ``initialize_sql`` which receives a SQL + database engine and binds it to our SQLAlchemy DBSession object. It also + calls the ``populate`` function, to do initial database population. diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index e1d0ab598..23b01a338 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -5,8 +5,7 @@ debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/tutorial.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = @@ -18,3 +17,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index 771b2e3d7..dbac349b9 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -2,7 +2,7 @@ from pyramid.configuration import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy -from pyramid.settings import asbool +from sqlalchemy import engine_from_config from tutorial.models import initialize_sql from tutorial.security import groupfinder @@ -10,11 +10,8 @@ from tutorial.security import groupfinder def main(global_config, **settings): """ This function returns a WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - initialize_sql(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py index 7580220b6..487299c4c 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -3,7 +3,6 @@ import transaction from pyramid.security import Allow from pyramid.security import Everyone -from sqlalchemy import create_engine from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import Text @@ -30,8 +29,7 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(db_string, echo=False): - engine = create_engine(db_string, echo=echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index e1d0ab598..23b01a338 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -5,8 +5,7 @@ debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/tutorial.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = @@ -18,3 +17,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py index 0ae61fccd..5236a538d 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -1,16 +1,13 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool +from sqlalchemy import engine_from_config from tutorial.models import initialize_sql def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - initialize_sql(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.my_view', diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py index a1726ebf4..9da906752 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py @@ -1,6 +1,5 @@ import transaction -from sqlalchemy import create_engine from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import Unicode @@ -33,8 +32,7 @@ def populate(): session.flush() transaction.commit() -def initialize_sql(db_string, db_echo=False): - engine = create_engine(db_string, echo=db_echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py index 72f0c89d8..2db1bc5b6 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py @@ -3,8 +3,9 @@ from pyramid.configuration import Configurator from pyramid import testing def _initTestingDB(): + from sqlalchemy import create_engine from tutorial.models import initialize_sql - session = initialize_sql('sqlite://') + session = initialize_sql(create_engine('sqlite://')) return session class TestMyView(unittest.TestCase): diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index e1d0ab598..23b01a338 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -5,8 +5,7 @@ debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/tutorial.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = @@ -18,3 +17,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 6e291ffdf..e1baa2d64 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -1,16 +1,13 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool +from sqlalchemy import engine_from_config from tutorial.models import initialize_sql def main(global_config, **settings): """ This function returns a WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - initialize_sql(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.my_view', diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index ec9d2b25c..23b8afab8 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -1,6 +1,5 @@ import transaction -from sqlalchemy import create_engine from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import Text @@ -27,8 +26,7 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(db_string, echo=False): - engine = create_engine(db_string, echo=echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index e1d0ab598..23b01a338 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -5,8 +5,7 @@ debug_authorization = false debug_notfound = false debug_templates = true default_locale_name = en -db_string = sqlite:///%(here)s/tutorial.db -db_echo = false +sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = @@ -18,3 +17,37 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index aa75c359a..91c299e24 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -1,16 +1,13 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool +from sqlalchemy import engine_from_config from tutorial.models import initialize_sql def main(global_config, **settings): """ This function returns a WSGI application. """ - db_string = settings.get('db_string') - if db_string is None: - raise ValueError("No 'db_string' value in application configuration.") - db_echo = settings.get('db_echo', 'false') - initialize_sql(db_string, asbool(db_echo)) + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') config.add_route('home', '/', view='tutorial.views.view_wiki') diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py index ec9d2b25c..23b8afab8 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -1,6 +1,5 @@ import transaction -from sqlalchemy import create_engine from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import Text @@ -27,8 +26,7 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(db_string, echo=False): - engine = create_engine(db_string, echo=echo) +def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) diff --git a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl index aabbce27b..f6591ab32 100755 --- a/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/alchemy/+package+/__init__.py_tmpl @@ -1,5 +1,4 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool from sqlalchemy import engine_from_config from {{package}}.models import appmaker diff --git a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl index 867dd8b3e..b67a2c35b 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/pylons_sqla/+package+/__init__.py_tmpl @@ -1,5 +1,4 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool from pyramid_beaker import session_factory_from_settings diff --git a/pyramid/paster_templates/pylons_sqla/+package+/tests.py_tmpl b/pyramid/paster_templates/pylons_sqla/+package+/tests.py_tmpl index 099df47bd..b55760b37 100644 --- a/pyramid/paster_templates/pylons_sqla/+package+/tests.py_tmpl +++ b/pyramid/paster_templates/pylons_sqla/+package+/tests.py_tmpl @@ -3,8 +3,9 @@ import unittest class MyHandlerTests(unittest.TestCase): def setUp(self): from pyramid.configuration import Configurator + from sqlalchemy import create_engine from {{package}}.models import initialize_sql - self.session = initialize_sql('sqlite://') + self.session = initialize_sql(create_engine('sqlite://')) self.config = Configurator() self.config.begin() diff --git a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl index f5ce7d77b..5c326caa8 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/__init__.py_tmpl @@ -1,5 +1,4 @@ from pyramid.configuration import Configurator -from pyramid.settings import asbool from sqlalchemy import engine_from_config from {{package}}.models import initialize_sql diff --git a/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl b/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl index 142341333..cfab37670 100644 --- a/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl +++ b/pyramid/paster_templates/routesalchemy/+package+/tests.py_tmpl @@ -3,8 +3,9 @@ from pyramid.configuration import Configurator from pyramid import testing def _initTestingDB(): + from sqlalchemy import create_engine from {{package}}.models import initialize_sql - session = initialize_sql('sqlite://') + session = initialize_sql(create_engine('sqlite://')) return session class TestMyView(unittest.TestCase): -- cgit v1.2.3 From 5369c85e1153fadbc2db22975c30bd1050e28d7a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 17:52:52 -0500 Subject: gardening --- TODO.txt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/TODO.txt b/TODO.txt index 0ed31a312..aee7d09da 100644 --- a/TODO.txt +++ b/TODO.txt @@ -17,25 +17,6 @@ Must-Have (before 1.0) - Use ``@register_view`` instead of ``@view_config`` and change view docs to use "view registration" instead of "view configuration". -- SQLAlchemy idiomatics: - - mcdonc: those paster templates all look pretty good... the - only thing i'd consider is adjusting your config variable names to match - exactly what sqlalchemy uses as parameter names, see here: - http://www.sqlalchemy.org/docs/core/engines.html - - mcdonc: especially in the pylons_sqla ini file, where the db - initialization is mixed in w/ the app config... - - ... i'd use "sqlalchemy.PARAMETER" for all of the sqla - settings, so it could easily be handed to engine_from_config w/o any need - to parse by hand - - mcdonc: in the other ini files, where sqlalchemy is given its - own part, the "sqlalchemy." prefix probably isn't necessary, but matching - the parameter names (e.g. 'url' instead of 'db_string') is still probably - a good idea - - Better ``config.add_handler`` documentation. Should-Have -- cgit v1.2.3 From e269038f5328f80edaa7cceb73b8a176204ecc63 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 18:15:43 -0500 Subject: - Add ``pyramid.interfaces.ITemplateRenderer`` interface to Interfaces API chapter (has ``implementation()`` method, required to be used when getting at Chameleon macros). --- CHANGES.txt | 4 ++++ docs/api/interfaces.rst | 1 + pyramid/interfaces.py | 40 ++++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9fa66ce1f..9d9a9ee5a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,6 +26,10 @@ Documentation - SQLAlchemy+URLDispatch tutorial updated to integrate changes to ``pyramid_routesalchemy`` template. +- Add ``pyramid.interfaces.ITemplateRenderer`` interface to Interfaces API + chapter (has ``implementation()`` method, required to be used when getting + at Chameleon macros). + 1.0a4 (2010-11-21) ================== diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index b27428d89..b3c14e5f7 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -33,4 +33,5 @@ Other Interfaces .. autointerface:: IRendererInfo + .. autointerface:: ITemplateRenderer diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 9b895e020..f021ba60b 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -100,6 +100,26 @@ class IBeforeRender(Interface): """ Return the value for key ``k`` from the renderer globals dictionary, or the default if no such value exists.""" +class IRenderer(Interface): + def __call__(value, system): + """ Call a the renderer implementation with the result of the + view (``value``) passed in and return a result (a string or + unicode object useful as a response body). Values computed by + the system are passed by the system in the ``system`` + parameter, which is a dictionary. Keys in the dictionary + include: ``view`` (the view callable that returned the value), + ``renderer_name`` (the template name or simple name of the + renderer), ``context`` (the context object passed to the + view), and ``request`` (the request object passed to the + view).""" + +class ITemplateRenderer(IRenderer): + def implementation(): + """ Return the object that the underlying templating system + uses to render the template; it is typically a callable that + accepts arbitrary keyword arguments and returns a string or + unicode object """ + # internal interfaces class IRequest(Interface): @@ -233,19 +253,6 @@ class ITraverser(Interface): ITraverserFactory = ITraverser # b / c for 1.0 code -class IRenderer(Interface): - def __call__(value, system): - """ Call a the renderer implementation with the result of the - view (``value``) passed in and return a result (a string or - unicode object useful as a response body). Values computed by - the system are passed by the system in the ``system`` - parameter, which is a dictionary. Keys in the dictionary - include: ``view`` (the view callable that returned the value), - ``renderer_name`` (the template name or simple name of the - renderer), ``context`` (the context object passed to the - view), and ``request`` (the request object passed to the - view).""" - class IRendererFactory(Interface): def __call__(name): """ Return an object that implements ``IRenderer`` """ @@ -259,13 +266,6 @@ class IRendererGlobalsFactory(Interface): ``renderer_name``, which will be the name of the renderer in use.""" -class ITemplateRenderer(IRenderer): - def implementation(): - """ Return the object that the underlying templating system - uses to render the template; it is typically a callable that - accepts arbitrary keyword arguments and returns a string or - unicode object """ - class IViewPermission(Interface): def __call__(context, request): """ Return True if the permission allows, return False if it denies. """ -- cgit v1.2.3 From 76e48bbbde9891aa98205d4f9fd8c47d15a7f0eb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 21:48:00 -0500 Subject: - Make default renderer work (renderer factory registered with no name, which is active for every view unless the view names a specific renderer). --- CHANGES.txt | 3 +++ TODO.txt | 3 --- pyramid/configuration.py | 18 +++++++++++++++--- pyramid/tests/test_configuration.py | 37 +++++++++++++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9d9a9ee5a..ee36776c7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,9 @@ Bug Fixes - The ``pyramid_routesalchemy`` paster template's unit tests failed (``AssertionError: 'SomeProject' != 'someproject'``). This is fixed. +- Make default renderer work (renderer factory registered with no name, which + is active for every view unless the view names a specific renderer). + Documentation ------------- diff --git a/TODO.txt b/TODO.txt index aee7d09da..41ea2c9e4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,9 +11,6 @@ Must-Have (before 1.0) - Provide a .flash API on session object. -- Make default renderer work (renderer registered with no name, which is - active for every view unless the view names a specific renderer). - - Use ``@register_view`` instead of ``@view_config`` and change view docs to use "view registration" instead of "view configuration". diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 4c3e8b04b..d6e6b136e 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -296,12 +296,17 @@ class Configurator(object): attr=None, renderer=None, wrapper_viewname=None, viewname=None, accept=None, order=MAX_ORDER, phash=DEFAULT_PHASH): + if renderer is None: # use default renderer if one exists + default_renderer_factory = self.registry.queryUtility( + IRendererFactory) + if default_renderer_factory is not None: + renderer = {'name':None, 'package':self.package} view = self.maybe_dotted(view) authn_policy = self.registry.queryUtility(IAuthenticationPolicy) authz_policy = self.registry.queryUtility(IAuthorizationPolicy) settings = self.registry.settings logger = self.registry.queryUtility(IDebugLogger) - mapped_view = _map_view(view, attr, renderer, self.registry) + mapped_view = _map_view(view, self.registry, attr, renderer) owrapped_view = _owrap_view(mapped_view, viewname, wrapper_viewname) secured_view = _secure_view(owrapped_view, permission, authn_policy, authz_policy) @@ -1591,7 +1596,9 @@ class Configurator(object): Add a :app:`Pyramid` :term:`renderer` factory to the current configuration state. - The ``name`` argument is the renderer name. + The ``name`` argument is the renderer name. Use ``None`` to + represent the default renderer (a renderer which will be used for all + views unless they name another renderer specifically). The ``factory`` argument is Python reference to an implementation of a :term:`renderer` factory or a @@ -1605,6 +1612,11 @@ class Configurator(object): to use this method. """ factory = self.maybe_dotted(factory) + # if name is None or the empty string, we're trying to register + # a default renderer, but registerUtility is too dumb to accept None + # as a name + if not name: + name = '' self.registry.registerUtility( factory, IRendererFactory, name=name, info=_info) @@ -2426,7 +2438,7 @@ def is_response(ob): return True return False -def _map_view(view, attr=None, renderer=None, registry=None): +def _map_view(view, registry, attr=None, renderer=None): wrapped_view = view helper = None diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index 4c1182eab..5113d1f4e 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -2558,6 +2558,36 @@ class ConfiguratorTests(unittest.TestCase): self.failIf(result is view) self.assertEqual(result(None, None).body, 'moo') + def test_derive_view_with_default_renderer_no_explicit_renderer(self): + def view(request): + return 'OK' + config = self._makeOne() + class moo(object): + def __init__(self, *arg, **kw): + pass + def __call__(self, *arg, **kw): + return 'moo' + config.add_renderer(None, moo) + result = config.derive_view(view) + self.failIf(result is view) + self.assertEqual(result(None, None).body, 'moo') + + def test_derive_view_with_default_renderer_with_explicit_renderer(self): + def view(request): + return 'OK' + config = self._makeOne() + class moo(object): pass + class foo(object): + def __init__(self, *arg, **kw): + pass + def __call__(self, *arg, **kw): + return 'foo' + config.add_renderer(None, moo) + config.add_renderer('foo', foo) + result = config.derive_view(view, renderer='foo') + self.failIf(result is view) + self.assertEqual(result(None, None).body, 'foo') + def test_derive_view_class_without_attr(self): class View(object): def __init__(self, request): @@ -3241,9 +3271,9 @@ class Test__map_view(unittest.TestCase): request.registry = self.registry return request - def _callFUT(self, *arg, **kw): + def _callFUT(self, view, **kw): from pyramid.configuration import _map_view - return _map_view(*arg, **kw) + return _map_view(view, self.registry, **kw) def test__map_view_as_function_context_and_request(self): def view(context, request): @@ -3542,8 +3572,7 @@ class Test__map_view(unittest.TestCase): def view(context, request): return {'a':'1'} info = {'name':renderer.spec, 'package':None} - result = self._callFUT(view, renderer=info, - registry=self.registry) + result = self._callFUT(view, renderer=info) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) -- cgit v1.2.3 From d15421c6a06e411d4ab7af3739440d5fa7556945 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 21:49:08 -0500 Subject: garden --- TODO.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 41ea2c9e4..ea7989d39 100644 --- a/TODO.txt +++ b/TODO.txt @@ -35,7 +35,9 @@ Should-Have - Change "Cleaning up After a Request" in the urldispatch chapter to use ``request.add_response_callback``. -- Twophase configuration (config = Configurator(autocommit=False)) +- Twophase configuration (config = Configurator(autocommit=False)). Maybe + use ``zope.configuration`` ConfigurationContext as config.registry.ctx and + push execution into the configurator. Nice-to-Have ------------ -- cgit v1.2.3 From 00fb8b0d7b5dbbf44d28b9af538dc81a58550f67 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 21 Nov 2010 22:23:44 -0500 Subject: add link in add_handler method to handlers chapter --- TODO.txt | 2 -- pyramid/configuration.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.txt b/TODO.txt index ea7989d39..4319e3cca 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,8 +14,6 @@ Must-Have (before 1.0) - Use ``@register_view`` instead of ``@view_config`` and change view docs to use "view registration" instead of "view configuration". -- Better ``config.add_handler`` documentation. - Should-Have ----------- diff --git a/pyramid/configuration.py b/pyramid/configuration.py index d6e6b136e..6ebb56ec3 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -704,6 +704,8 @@ class Configurator(object): Any extra keyword arguments are passed along to ``add_route``. + See :ref:`handlers_chapter` for more explanatory documentation. + This method returns the result of add_route.""" handler = self.maybe_dotted(handler) -- cgit v1.2.3 From c5f5572a5320ed0ddfc54ee4b03933b6280c9032 Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Mon, 22 Nov 2010 14:35:16 -0500 Subject: pyramid.testig.DummyRequest now has a class variable, query_string, + which defaults to the empty string. --- CHANGES.txt | 3 +++ pyramid/testing.py | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ee36776c7..2ea0ac448 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,6 +10,9 @@ Features templates now use idiomatic SQLAlchemy configuration in their respective ``.ini`` files and Python code. +- ``pyramid.testig.DummyRequest`` now has a class variable, ``query_string``, + which defaults to the empty string. + Bug Fixes --------- diff --git a/pyramid/testing.py b/pyramid/testing.py index 4acede879..cc0ee5c5b 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -41,7 +41,7 @@ def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True): argument. The authentication policy will return the userid identifier implied by the ``userid`` argument and the group ids implied by the ``groupids`` argument when the - :func:`pyramid.security.authenticated_userid` or + :func:`pyramid.security.authenticated_userid` or :func:`pyramid.security.effective_principals` APIs are used. This function is most useful when testing code that uses the APIs @@ -211,7 +211,7 @@ def registerAdapter(impl, for_=Interface, provides=Interface, name=''): The ``name`` argument is the empty string by default; it implies the name under which the adapter is registered. - + See `The ZCA book `_ for more information about ZCA adapters. @@ -282,7 +282,7 @@ def registerSettings(dictarg=None, **kw): registerSettings({'external_uri':'http://example.com'}) Or a set of key/value pairs:: - + registerSettings(external_uri='http://example.com') Use of this function is required when you need to test code that calls @@ -361,7 +361,7 @@ class DummyTemplateRenderer(object): def implementation(self): return self._implementation - + def __call__(self, kw, system=None): if system: self._received.update(system) @@ -393,7 +393,7 @@ class DummyTemplateRenderer(object): raise AssertionError( 'A value for key "%s" was not passed to the renderer' % k) - + if myval != v: raise AssertionError( '\nasserted value for %s: %r\nactual value: %r' % ( @@ -431,7 +431,7 @@ class DummyModel: val.__name__ = name val.__parent__ = self self.subs[name] = val - + def __getitem__(self, name): """ Return a named subobject (see ``__setitem__``)""" ob = self.subs[name] @@ -465,7 +465,7 @@ class DummyModel: def __contains__(self, name): return name in self.subs - + def clone(self, __name__=_marker, __parent__=_marker, **kw): """ Create a clone of the model object. If ``__name__`` or ``__parent__`` arguments are passed, use these values to @@ -485,7 +485,7 @@ class DummyModel: class DummyRequest(object): """ A dummy request object (imitates a :term:`request` object). - + The ``params``, ``environ``, ``headers``, ``path``, and ``cookies`` arguments correspond to their :term`WebOb` equivalents. @@ -503,6 +503,7 @@ class DummyRequest(object): application_url = 'http://example.com' host = 'example.com:80' content_length = 0 + query_string = '' response_callbacks = () def __init__(self, params=None, environ=None, headers=None, path='/', cookies=None, post=None, **kw): @@ -721,8 +722,8 @@ class DummyRendererFactory(object): raise KeyError('No testing renderer registered for %r' % spec) return renderer - - + + class MockTemplate(object): def __init__(self, response): self._received = {} @@ -750,7 +751,7 @@ def skip_on(*platforms): return decorator skip_on.os_name = os.name # for testing try: # pragma: no cover - import __pypy__ + import __pypy__ skip_on.pypy = True except ImportError: skip_on.pypy = False -- cgit v1.2.3 From b37e96b0c944cc87d61dd499b33b01dc3c85a2d5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 22 Nov 2010 15:18:20 -0500 Subject: changed cluegun; note in index page --- docs/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 96cdc647b..72c21bbc8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -117,8 +117,7 @@ Sample Applications application based on Rocky Burt's `ClueBin `_. It demonstrates form processing, security, and the use of :term:`ZODB` within a :app:`Pyramid` -application. It also has very simple :term:`repoze.who` integration. Check -this application out via:: +application. Check this application out via:: git clone git://github.com/Pylons/cluegun.git @@ -133,8 +132,9 @@ earlier version of this application runs the `repoze.org `shootout `_ is an example "idea competition" application by Carlos de la Guardia. It demonstrates a hybrid of :term:`URL dispatch` and :term:`traversal` and integration with -`SQLAlchemy `_ and :term:`repoze.who`. Check -this application out of version control via:: +`SQLAlchemy `_, :term:`repoze.who`, and +`Deliverance `_. Check this application +out of version control via:: git clone git://github.com/Pylons/shootout.git -- cgit v1.2.3 From b7bc0262cc51546cf0219520ef5aedee9b754971 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 22 Nov 2010 23:55:03 -0500 Subject: fix project section name doc bug --- docs/narr/project.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 6e466b284..aef134ff7 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -316,12 +316,13 @@ Python interpreter shell unconditionally. [pipeline:main] pipeline = egg:WebError#evalerror - myapp + MyProject - If you use ``main`` as the section name argument instead of ``myapp`` - against the above ``.ini`` file, an error will occur. Use the most - specific reference to your application within the ``.ini`` file possible - as the section name argument. + Use ``MyProject`` instead of ``main`` as the section name argument to + ``pshell`` against the above ``.ini`` file (e.g. ``paster pshell + development.ini MyProject``). If you use ``main`` instead, an error will + occur. Use the most specific reference to your application within the + ``.ini`` file possible as the section name argument. Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). -- cgit v1.2.3 From efbb06c2d2813f23f8fad68cf09123f1ab75d96d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 22 Nov 2010 23:57:38 -0500 Subject: fix wrong egg reference --- docs/narr/startup.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index c57525f4c..427acc319 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -86,9 +86,9 @@ press ``return`` after running ``paster serve development.ini``. :linenos: In this case, the ``myproject.run:app`` function referred to by the entry - point URI ``egg:MyProject#app`` (see :ref:`MyProject_ini` for more - information about entry point URIs, and how they relate to callables), - will receive the key/value pairs ``{'reload_templates':'true', + point URI ``egg:MyProject`` (see :ref:`MyProject_ini` for more information + about entry point URIs, and how they relate to callables), will receive + the key/value pairs ``{'reload_templates':'true', 'debug_authorization':'false', 'debug_notfound':'false', 'debug_templates':'true', 'default_locale_name':'en'}``. -- cgit v1.2.3 From 344ed58d2aa9f42ed64142d92ae5d79bf3a9f4e7 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 23 Nov 2010 00:00:22 -0500 Subject: fix quoting --- docs/narr/urldispatch.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index a86041e55..df7d592f9 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -349,7 +349,8 @@ also capture the remainder of the URL, for example: foo/{baz}/{bar}{fizzle:.*} -The above pattern will match these URLs, generating the following matchdicts: +The above pattern will match these URLs, generating the following +matchdicts:: foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':()} foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c')} -- cgit v1.2.3 From b22d1fed835897c28b0136253ae428d28e19e07c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 23 Nov 2010 00:03:39 -0500 Subject: gardening --- TODO.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.txt b/TODO.txt index 4319e3cca..c5584d3cc 100644 --- a/TODO.txt +++ b/TODO.txt @@ -108,3 +108,5 @@ Nice-to-Have - Add functionality that mocks the behavior of ``repoze.browserid``. +- One way to split up views chapter: views with renderers / views without + renderers. -- cgit v1.2.3 From 3f61fa08ae42860dde823e610134e4cc2b7ec991 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 23 Nov 2010 00:12:16 -0500 Subject: correct typo: ! -> ~ --- docs/narr/handlers.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/handlers.rst b/docs/narr/handlers.rst index 022f27115..d82f42bdb 100644 --- a/docs/narr/handlers.rst +++ b/docs/narr/handlers.rst @@ -168,8 +168,8 @@ information on the handler method which is used by configuration. All keyword arguments are recorded, and passed to -:meth:`!pyramid.configuration.Configurator.add_view`. Any valid keyword -arguments for :meth:`!pyramid.configuration.Configurator.add_view` can thus be +:meth:`~pyramid.configuration.Configurator.add_view`. Any valid keyword +arguments for :meth:`~pyramid.configuration.Configurator.add_view` can thus be used with the :class:`~pyramid.view.action` decorator to further restrict when the view will be called. -- cgit v1.2.3 From bc414e504a11e91da9e6aa3fdd7e3aea724104f9 Mon Sep 17 00:00:00 2001 From: Igor Sobreira Date: Tue, 23 Nov 2010 09:08:21 -0200 Subject: fixing typo in deprecation warning --- pyramid/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/settings.py b/pyramid/settings.py index 64b108421..928dd1915 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -88,7 +88,7 @@ def get_settings(): deprecated( 'get_settings', '(pyramid.settings.get_settings is deprecated as of Pyramid 1.0. Use' - '``pyramid.threadlocals.get_current_registry().settings`` instead or use ' + '``pyramid.threadlocal.get_current_registry().settings`` instead or use ' 'the ``settings`` attribute of the registry available from the request ' '(``request.registry.settings``)).') -- cgit v1.2.3 From e880a42099f1e3df1bd628058ba9e3a38a5ef0c8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 24 Nov 2010 03:15:22 -0500 Subject: - Add support for json on GAE by catching NotImplementedError and importing simplejson from django.utils. --- CHANGES.txt | 3 +++ pyramid/compat/__init__.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2ea0ac448..b8c3a847f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,9 @@ Features - ``pyramid.testig.DummyRequest`` now has a class variable, ``query_string``, which defaults to the empty string. +- Add support for json on GAE by catching NotImplementedError and importing + simplejson from django.utils. + Bug Fixes --------- diff --git a/pyramid/compat/__init__.py b/pyramid/compat/__init__.py index 205175132..8bbf79703 100644 --- a/pyramid/compat/__init__.py +++ b/pyramid/compat/__init__.py @@ -133,7 +133,12 @@ except NameError: # pragma: no cover try: import json except ImportError: # pragma: no cover - import simplejson as json + try: + import simplejson as json + except NotImplementedError: + from django.utils import simplejson as json # GAE + + try: from hashlib import md5 -- cgit v1.2.3 From a9f17c8b3a7fc935b70e87f2a1fb8728deb16f85 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 24 Nov 2010 18:42:35 -0500 Subject: - The Mako renderer did not properly turn the ``mako.imports``, ``mako.default_filters``, and ``mako.imports`` settings into lists. - The Mako renderer did not properly convert the ``mako.error_handler`` setting from a dotted name to a callable. - The Mako renderer now accepts a resource specification for ``mako.module_directory``. --- CHANGES.txt | 9 ++ pyramid/configuration.py | 141 +-------------------------- pyramid/mako_templating.py | 19 +++- pyramid/tests/test_configuration.py | 175 --------------------------------- pyramid/tests/test_mako_templating.py | 92 +++++++++++++++++- pyramid/tests/test_util.py | 177 ++++++++++++++++++++++++++++++++++ pyramid/util.py | 144 +++++++++++++++++++++++++++ 7 files changed, 437 insertions(+), 320 deletions(-) create mode 100644 pyramid/tests/test_util.py create mode 100644 pyramid/util.py diff --git a/CHANGES.txt b/CHANGES.txt index b8c3a847f..547a7254a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,9 @@ Features - Add support for json on GAE by catching NotImplementedError and importing simplejson from django.utils. +- The Mako renderer now accepts a resource specification for + ``mako.module_directory``. + Bug Fixes --------- @@ -25,6 +28,12 @@ Bug Fixes - Make default renderer work (renderer factory registered with no name, which is active for every view unless the view names a specific renderer). +- The Mako renderer did not properly turn the ``mako.imports``, + ``mako.default_filters``, and ``mako.imports`` settings into lists. + +- The Mako renderer did not properly convert the ``mako.error_handler`` + setting from a dotted name to a callable. + Documentation ------------- diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 6ebb56ec3..29a322c7f 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -3,7 +3,6 @@ import re import sys import threading import inspect -import pkg_resources import venusian @@ -67,7 +66,6 @@ from pyramid.i18n import get_localizer from pyramid.log import make_stream_logger from pyramid.mako_templating import renderer_factory as mako_renderer_factory from pyramid.path import caller_package -from pyramid.path import package_of from pyramid.path import package_path from pyramid.registry import Registry from pyramid.renderers import RendererHelper @@ -83,6 +81,7 @@ from pyramid.traversal import DefaultRootFactory from pyramid.traversal import find_interface from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper +from pyramid.util import DottedNameResolver from pyramid.view import default_exceptionresponse_view from pyramid.view import render_view_to_response @@ -2727,144 +2726,6 @@ def make_app(root_factory, package=None, filename='configure.zcml', config.end() return config.make_wsgi_app() -class DottedNameResolver(object): - """ This class resolves dotted name references to 'global' Python - objects (objects which can be imported) to those objects. - - Two dotted name styles are supported during deserialization: - - - ``pkg_resources``-style dotted names where non-module attributes - of a package are separated from the rest of the path using a ':' - e.g. ``package.module:attr``. - - - ``zope.dottedname``-style dotted names where non-module - attributes of a package are separated from the rest of the path - using a '.' e.g. ``package.module.attr``. - - These styles can be used interchangeably. If the serialization - contains a ``:`` (colon), the ``pkg_resources`` resolution - mechanism will be chosen, otherwise the ``zope.dottedname`` - resolution mechanism will be chosen. - - The constructor accepts a single argument named ``package`` which - should be a one of: - - - a Python module or package object - - - A fully qualified (not relative) dotted name to a module or package - - - The value ``None`` - - The ``package`` is used when relative dotted names are supplied to - the resolver's ``resolve`` and ``maybe_resolve`` methods. A - dotted name which has a ``.`` (dot) or ``:`` (colon) as its first - character is treated as relative. - - If the value ``None`` is supplied as the package name, the - resolver will only be able to resolve fully qualified (not - relative) names. Any attempt to resolve a relative name when the - ``package`` is ``None`` will result in an - :exc:`pyramid.configuration.ConfigurationError` exception. - - If a *module* or *module name* (as opposed to a package or package - name) is supplied as ``package``, its containing package is - computed and this package used to derive the package name (all - names are resolved relative to packages, never to modules). For - example, if the ``package`` argument to this type was passed the - string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to - the ``resolve`` method, the resulting import would be for - ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module - object, not a package object. - - If a *package* or *package name* (as opposed to a module or module - name) is supplied as ``package``, this package will be used to - relative compute dotted names. For example, if the ``package`` - argument to this type was passed the string ``xml.dom``, and - ``.minidom`` is supplied to the ``resolve`` method, the resulting - import would be for ``xml.minidom``. - - When a dotted name cannot be resolved, a - :class:`pyramid.exceptions.ConfigurationError` error is raised. - """ - def __init__(self, package): - if package is None: - self.package_name = None - self.package = None - else: - if isinstance(package, basestring): - try: - __import__(package) - except ImportError: - raise ConfigurationError( - 'The dotted name %r cannot be imported' % (package,)) - package = sys.modules[package] - self.package = package_of(package) - self.package_name = self.package.__name__ - - def _pkg_resources_style(self, value): - """ package.module:attr style """ - if value.startswith('.') or value.startswith(':'): - if not self.package_name: - raise ConfigurationError( - 'relative name %r irresolveable without ' - 'package_name' % (value,)) - if value in ['.', ':']: - value = self.package_name - else: - value = self.package_name + value - return pkg_resources.EntryPoint.parse( - 'x=%s' % value).load(False) - - def _zope_dottedname_style(self, value): - """ package.module.attr style """ - module = self.package_name and self.package_name or None - if value == '.': - if self.package_name is None: - raise ConfigurationError( - 'relative name %r irresolveable without package' % (value,) - ) - name = module.split('.') - else: - name = value.split('.') - if not name[0]: - if module is None: - raise ConfigurationError( - 'relative name %r irresolveable without ' - 'package' % (value,) - ) - module = module.split('.') - name.pop(0) - while not name[0]: - module.pop() - name.pop(0) - name = module + name - - used = name.pop(0) - found = __import__(used) - for n in name: - used += '.' + n - try: - found = getattr(found, n) - except AttributeError: - __import__(used) - found = getattr(found, n) # pragma: no cover - - return found - - def resolve(self, dotted): - if not isinstance(dotted, basestring): - raise ConfigurationError('%r is not a string' % (dotted,)) - return self.maybe_resolve(dotted) - - def maybe_resolve(self, dotted): - if isinstance(dotted, basestring): - if ':' in dotted: - return self._pkg_resources_style(dotted) - else: - return self._zope_dottedname_style(dotted) - return dotted - - class ActionPredicate(object): action_name = 'action' def __init__(self, action): diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index 6356e0db4..c9c041edb 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -8,6 +8,7 @@ from pyramid.interfaces import ITemplateRenderer from pyramid.exceptions import ConfigurationError from pyramid.resource import resolve_resource_spec from pyramid.resource import abspath_from_resource_spec +from pyramid.util import DottedNameResolver from mako.lookup import TemplateLookup from mako import exceptions @@ -61,17 +62,27 @@ def renderer_factory(info): lookup = registry.queryUtility(IMakoLookup) if lookup is None: reload_templates = settings.get('reload_templates', False) - directories = settings.get('mako.directories') - module_directory = settings.get('mako.module_directory') + directories = settings.get('mako.directories', None) + module_directory = settings.get('mako.module_directory', None) input_encoding = settings.get('mako.input_encoding', 'utf-8') error_handler = settings.get('mako.error_handler', None) default_filters = settings.get('mako.default_filters', None) - imports = settings.get('mako.imports', []) + imports = settings.get('mako.imports', None) if directories is None: raise ConfigurationError( 'Mako template used without a ``mako.directories`` setting') - directories = directories.splitlines() + directories = filter(None, directories.splitlines()) directories = [ abspath_from_resource_spec(d) for d in directories ] + if module_directory is not None: + module_directory = abspath_from_resource_spec(module_directory) + if error_handler is not None: + dotted = DottedNameResolver(info.package) + error_handler = dotted.maybe_resolve(error_handler) + if default_filters is not None: + default_filters = filter(None, default_filters.splitlines()) + if imports: + imports = filter(None, imports.splitlines()) + lookup = PkgResourceTemplateLookup(directories=directories, module_directory=module_directory, input_encoding=input_encoding, diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index 5113d1f4e..938db3c31 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -4286,181 +4286,6 @@ class TestMakeApp(unittest.TestCase): Configurator=DummyConfigurator) self.assertEqual(app.zcml_file, '2.zcml') -class TestDottedNameResolver(unittest.TestCase): - def _makeOne(self, package=None): - from pyramid.configuration import DottedNameResolver - return DottedNameResolver(package) - - def config_exc(self, func, *arg, **kw): - from pyramid.exceptions import ConfigurationError - try: - func(*arg, **kw) - except ConfigurationError, e: - return e - else: - raise AssertionError('Invalid not raised') # pragma: no cover - - def test_zope_dottedname_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - 'pyramid.tests.test_configuration.TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test_zope_dottedname_style_irrresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises(ImportError, typ._zope_dottedname_style, - 'pyramid.test_configuration.nonexisting_name') - - def test__zope_dottedname_style_resolve_relative(self): - import pyramid.tests - typ = self._makeOne(package=pyramid.tests) - result = typ._zope_dottedname_style( - '.test_configuration.TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test__zope_dottedname_style_resolve_relative_leading_dots(self): - import pyramid.tests.test_configuration - typ = self._makeOne(package=pyramid.tests) - result = typ._zope_dottedname_style( - '..tests.test_configuration.TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test__zope_dottedname_style_resolve_relative_is_dot(self): - import pyramid.tests - typ = self._makeOne(package=pyramid.tests) - result = typ._zope_dottedname_style('.') - self.assertEqual(result, pyramid.tests) - - def test__zope_dottedname_style_irresolveable_relative_is_dot(self): - typ = self._makeOne() - e = self.config_exc(typ._zope_dottedname_style, '.') - self.assertEqual( - e.args[0], - "relative name '.' irresolveable without package") - - def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self): - typ = self._makeOne() - e = self.config_exc(typ._zope_dottedname_style, '.whatever') - self.assertEqual( - e.args[0], - "relative name '.whatever' irresolveable without package") - - def test_zope_dottedname_style_irrresolveable_relative(self): - import pyramid.tests - typ = self._makeOne(package=pyramid.tests) - self.assertRaises(ImportError, typ._zope_dottedname_style, - '.notexisting') - - def test__zope_dottedname_style_resolveable_relative(self): - import pyramid - typ = self._makeOne(package=pyramid) - result = typ._zope_dottedname_style('.tests') - from pyramid import tests - self.assertEqual(result, tests) - - def test__zope_dottedname_style_irresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises( - ImportError, - typ._zope_dottedname_style, 'pyramid.fudge.bar') - - def test__zope_dottedname_style_resolveable_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - 'pyramid.tests.test_configuration.TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._pkg_resources_style( - 'pyramid.tests.test_configuration:TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_irrresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises(ImportError, typ._pkg_resources_style, - 'pyramid.tests:nonexisting') - - def test__pkg_resources_style_resolve_relative(self): - import pyramid.tests - typ = self._makeOne(package=pyramid.tests) - result = typ._pkg_resources_style( - '.test_configuration:TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_resolve_relative_is_dot(self): - import pyramid.tests - typ = self._makeOne(package=pyramid.tests) - result = typ._pkg_resources_style('.') - self.assertEqual(result, pyramid.tests) - - def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): - typ = self._makeOne() - from pyramid.exceptions import ConfigurationError - self.assertRaises(ConfigurationError, typ._pkg_resources_style, - '.whatever') - - def test__pkg_resources_style_irrresolveable_relative(self): - import pyramid - typ = self._makeOne(package=pyramid) - self.assertRaises(ImportError, typ._pkg_resources_style, - ':notexisting') - - def test_resolve_not_a_string(self): - typ = self._makeOne() - e = self.config_exc(typ.resolve, None) - self.assertEqual(e.args[0], 'None is not a string') - - def test_resolve_using_pkgresources_style(self): - typ = self._makeOne() - result = typ.resolve( - 'pyramid.tests.test_configuration:TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test_resolve_using_zope_dottedname_style(self): - typ = self._makeOne() - result = typ.resolve( - 'pyramid.tests.test_configuration:TestDottedNameResolver') - self.assertEqual(result, self.__class__) - - def test_resolve_missing_raises(self): - typ = self._makeOne() - self.assertRaises(ImportError, typ.resolve, 'cant.be.found') - - def test_ctor_string_module_resolveable(self): - import pyramid.tests - typ = self._makeOne('pyramid.tests.test_configuration') - self.assertEqual(typ.package, pyramid.tests) - self.assertEqual(typ.package_name, 'pyramid.tests') - - def test_ctor_string_package_resolveable(self): - import pyramid.tests - typ = self._makeOne('pyramid.tests') - self.assertEqual(typ.package, pyramid.tests) - self.assertEqual(typ.package_name, 'pyramid.tests') - - def test_ctor_string_irresolveable(self): - from pyramid.configuration import ConfigurationError - self.assertRaises(ConfigurationError, self._makeOne, 'cant.be.found') - - def test_ctor_module(self): - import pyramid.tests - import pyramid.tests.test_configuration - typ = self._makeOne(pyramid.tests.test_configuration) - self.assertEqual(typ.package, pyramid.tests) - self.assertEqual(typ.package_name, 'pyramid.tests') - - def test_ctor_package(self): - import pyramid.tests - typ = self._makeOne(pyramid.tests) - self.assertEqual(typ.package, pyramid.tests) - self.assertEqual(typ.package_name, 'pyramid.tests') - - def test_ctor_None(self): - typ = self._makeOne(None) - self.assertEqual(typ.package, None) - self.assertEqual(typ.package_name, None) - class Test_isexception(unittest.TestCase): def _callFUT(self, ob): from pyramid.configuration import isexception diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index e0c02550b..cfb54b902 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -47,7 +47,7 @@ class Test_renderer_factory(Base, unittest.TestCase): def test_composite_directories_path(self): from pyramid.mako_templating import IMakoLookup - twice = self.templates_dir + '\n' + self.templates_dir + twice = '\n' + self.templates_dir + '\n' + self.templates_dir + '\n' settings = {'mako.directories':twice} info = DummyRendererInfo({ 'name':'helloworld.mak', @@ -59,6 +59,96 @@ class Test_renderer_factory(Base, unittest.TestCase): lookup = self.config.registry.getUtility(IMakoLookup) self.assertEqual(lookup.directories, [self.templates_dir]*2) + def test_with_module_directory_resource_spec(self): + import os + from pyramid.mako_templating import IMakoLookup + module_directory = 'pyramid.tests:fixtures' + settings = {'mako.directories':self.templates_dir, + 'mako.module_directory':module_directory} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + fixtures = os.path.join(os.path.dirname(__file__), 'fixtures') + self.assertEqual(lookup.module_directory, fixtures) + + def test_with_module_directory_resource_abspath(self): + import os + from pyramid.mako_templating import IMakoLookup + fixtures = os.path.join(os.path.dirname(__file__), 'fixtures') + settings = {'mako.directories':self.templates_dir, + 'mako.module_directory':fixtures} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.module_directory, fixtures) + + def test_with_input_encoding(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.input_encoding':'utf-16'} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['input_encoding'], 'utf-16') + + def test_with_error_handler(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.error_handler':'pyramid.tests'} + import pyramid.tests + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['error_handler'], pyramid.tests) + + def test_with_default_filters(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.default_filters':'\nh\ng\n\n'} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['default_filters'], ['h', 'g']) + + def test_with_imports(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.imports':'\none\ntwo\n\n'} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['imports'], ['one', 'two']) + def test_with_lookup(self): from pyramid.mako_templating import IMakoLookup lookup = dict() diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py new file mode 100644 index 000000000..2929f888f --- /dev/null +++ b/pyramid/tests/test_util.py @@ -0,0 +1,177 @@ +import unittest + +class TestDottedNameResolver(unittest.TestCase): + def _makeOne(self, package=None): + from pyramid.util import DottedNameResolver + return DottedNameResolver(package) + + def config_exc(self, func, *arg, **kw): + from pyramid.exceptions import ConfigurationError + try: + func(*arg, **kw) + except ConfigurationError, e: + return e + else: + raise AssertionError('Invalid not raised') # pragma: no cover + + def test_zope_dottedname_style_resolve_absolute(self): + typ = self._makeOne() + result = typ._zope_dottedname_style( + 'pyramid.tests.test_util.TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_zope_dottedname_style_irrresolveable_absolute(self): + typ = self._makeOne() + self.assertRaises(ImportError, typ._zope_dottedname_style, + 'pyramid.test_util.nonexisting_name') + + def test__zope_dottedname_style_resolve_relative(self): + import pyramid.tests + typ = self._makeOne(package=pyramid.tests) + result = typ._zope_dottedname_style( + '.test_util.TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__zope_dottedname_style_resolve_relative_leading_dots(self): + import pyramid.tests.test_configuration + typ = self._makeOne(package=pyramid.tests) + result = typ._zope_dottedname_style( + '..tests.test_util.TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__zope_dottedname_style_resolve_relative_is_dot(self): + import pyramid.tests + typ = self._makeOne(package=pyramid.tests) + result = typ._zope_dottedname_style('.') + self.assertEqual(result, pyramid.tests) + + def test__zope_dottedname_style_irresolveable_relative_is_dot(self): + typ = self._makeOne() + e = self.config_exc(typ._zope_dottedname_style, '.') + self.assertEqual( + e.args[0], + "relative name '.' irresolveable without package") + + def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self): + typ = self._makeOne() + e = self.config_exc(typ._zope_dottedname_style, '.whatever') + self.assertEqual( + e.args[0], + "relative name '.whatever' irresolveable without package") + + def test_zope_dottedname_style_irrresolveable_relative(self): + import pyramid.tests + typ = self._makeOne(package=pyramid.tests) + self.assertRaises(ImportError, typ._zope_dottedname_style, + '.notexisting') + + def test__zope_dottedname_style_resolveable_relative(self): + import pyramid + typ = self._makeOne(package=pyramid) + result = typ._zope_dottedname_style('.tests') + from pyramid import tests + self.assertEqual(result, tests) + + def test__zope_dottedname_style_irresolveable_absolute(self): + typ = self._makeOne() + self.assertRaises( + ImportError, + typ._zope_dottedname_style, 'pyramid.fudge.bar') + + def test__zope_dottedname_style_resolveable_absolute(self): + typ = self._makeOne() + result = typ._zope_dottedname_style( + 'pyramid.tests.test_util.TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_resolve_absolute(self): + typ = self._makeOne() + result = typ._pkg_resources_style( + 'pyramid.tests.test_util:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_irrresolveable_absolute(self): + typ = self._makeOne() + self.assertRaises(ImportError, typ._pkg_resources_style, + 'pyramid.tests:nonexisting') + + def test__pkg_resources_style_resolve_relative(self): + import pyramid.tests + typ = self._makeOne(package=pyramid.tests) + result = typ._pkg_resources_style( + '.test_util:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_resolve_relative_is_dot(self): + import pyramid.tests + typ = self._makeOne(package=pyramid.tests) + result = typ._pkg_resources_style('.') + self.assertEqual(result, pyramid.tests) + + def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): + typ = self._makeOne() + from pyramid.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, typ._pkg_resources_style, + '.whatever') + + def test__pkg_resources_style_irrresolveable_relative(self): + import pyramid + typ = self._makeOne(package=pyramid) + self.assertRaises(ImportError, typ._pkg_resources_style, + ':notexisting') + + def test_resolve_not_a_string(self): + typ = self._makeOne() + e = self.config_exc(typ.resolve, None) + self.assertEqual(e.args[0], 'None is not a string') + + def test_resolve_using_pkgresources_style(self): + typ = self._makeOne() + result = typ.resolve( + 'pyramid.tests.test_util:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_resolve_using_zope_dottedname_style(self): + typ = self._makeOne() + result = typ.resolve( + 'pyramid.tests.test_util:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_resolve_missing_raises(self): + typ = self._makeOne() + self.assertRaises(ImportError, typ.resolve, 'cant.be.found') + + def test_ctor_string_module_resolveable(self): + import pyramid.tests + typ = self._makeOne('pyramid.tests.test_util') + self.assertEqual(typ.package, pyramid.tests) + self.assertEqual(typ.package_name, 'pyramid.tests') + + def test_ctor_string_package_resolveable(self): + import pyramid.tests + typ = self._makeOne('pyramid.tests') + self.assertEqual(typ.package, pyramid.tests) + self.assertEqual(typ.package_name, 'pyramid.tests') + + def test_ctor_string_irresolveable(self): + from pyramid.configuration import ConfigurationError + self.assertRaises(ConfigurationError, self._makeOne, 'cant.be.found') + + def test_ctor_module(self): + import pyramid.tests + import pyramid.tests.test_util + typ = self._makeOne(pyramid.tests.test_util) + self.assertEqual(typ.package, pyramid.tests) + self.assertEqual(typ.package_name, 'pyramid.tests') + + def test_ctor_package(self): + import pyramid.tests + typ = self._makeOne(pyramid.tests) + self.assertEqual(typ.package, pyramid.tests) + self.assertEqual(typ.package_name, 'pyramid.tests') + + def test_ctor_None(self): + typ = self._makeOne(None) + self.assertEqual(typ.package, None) + self.assertEqual(typ.package_name, None) + diff --git a/pyramid/util.py b/pyramid/util.py new file mode 100644 index 000000000..84a23bb23 --- /dev/null +++ b/pyramid/util.py @@ -0,0 +1,144 @@ +import pkg_resources +import sys + +from pyramid.exceptions import ConfigurationError +from pyramid.path import package_of + +class DottedNameResolver(object): + """ This class resolves dotted name references to 'global' Python + objects (objects which can be imported) to those objects. + + Two dotted name styles are supported during deserialization: + + - ``pkg_resources``-style dotted names where non-module attributes + of a package are separated from the rest of the path using a ':' + e.g. ``package.module:attr``. + + - ``zope.dottedname``-style dotted names where non-module + attributes of a package are separated from the rest of the path + using a '.' e.g. ``package.module.attr``. + + These styles can be used interchangeably. If the serialization + contains a ``:`` (colon), the ``pkg_resources`` resolution + mechanism will be chosen, otherwise the ``zope.dottedname`` + resolution mechanism will be chosen. + + The constructor accepts a single argument named ``package`` which + should be a one of: + + - a Python module or package object + + - A fully qualified (not relative) dotted name to a module or package + + - The value ``None`` + + The ``package`` is used when relative dotted names are supplied to + the resolver's ``resolve`` and ``maybe_resolve`` methods. A + dotted name which has a ``.`` (dot) or ``:`` (colon) as its first + character is treated as relative. + + If the value ``None`` is supplied as the package name, the + resolver will only be able to resolve fully qualified (not + relative) names. Any attempt to resolve a relative name when the + ``package`` is ``None`` will result in an + :exc:`pyramid.configuration.ConfigurationError` exception. + + If a *module* or *module name* (as opposed to a package or package + name) is supplied as ``package``, its containing package is + computed and this package used to derive the package name (all + names are resolved relative to packages, never to modules). For + example, if the ``package`` argument to this type was passed the + string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to + the ``resolve`` method, the resulting import would be for + ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module + object, not a package object. + + If a *package* or *package name* (as opposed to a module or module + name) is supplied as ``package``, this package will be used to + relative compute dotted names. For example, if the ``package`` + argument to this type was passed the string ``xml.dom``, and + ``.minidom`` is supplied to the ``resolve`` method, the resulting + import would be for ``xml.minidom``. + + When a dotted name cannot be resolved, a + :class:`pyramid.exceptions.ConfigurationError` error is raised. + """ + def __init__(self, package): + if package is None: + self.package_name = None + self.package = None + else: + if isinstance(package, basestring): + try: + __import__(package) + except ImportError: + raise ConfigurationError( + 'The dotted name %r cannot be imported' % (package,)) + package = sys.modules[package] + self.package = package_of(package) + self.package_name = self.package.__name__ + + def _pkg_resources_style(self, value): + """ package.module:attr style """ + if value.startswith('.') or value.startswith(':'): + if not self.package_name: + raise ConfigurationError( + 'relative name %r irresolveable without ' + 'package_name' % (value,)) + if value in ['.', ':']: + value = self.package_name + else: + value = self.package_name + value + return pkg_resources.EntryPoint.parse( + 'x=%s' % value).load(False) + + def _zope_dottedname_style(self, value): + """ package.module.attr style """ + module = self.package_name and self.package_name or None + if value == '.': + if self.package_name is None: + raise ConfigurationError( + 'relative name %r irresolveable without package' % (value,) + ) + name = module.split('.') + else: + name = value.split('.') + if not name[0]: + if module is None: + raise ConfigurationError( + 'relative name %r irresolveable without ' + 'package' % (value,) + ) + module = module.split('.') + name.pop(0) + while not name[0]: + module.pop() + name.pop(0) + name = module + name + + used = name.pop(0) + found = __import__(used) + for n in name: + used += '.' + n + try: + found = getattr(found, n) + except AttributeError: + __import__(used) + found = getattr(found, n) # pragma: no cover + + return found + + def resolve(self, dotted): + if not isinstance(dotted, basestring): + raise ConfigurationError('%r is not a string' % (dotted,)) + return self.maybe_resolve(dotted) + + def maybe_resolve(self, dotted): + if isinstance(dotted, basestring): + if ':' in dotted: + return self._pkg_resources_style(dotted) + else: + return self._zope_dottedname_style(dotted) + return dotted + + -- cgit v1.2.3 From 0cb7acf94d429a6019871308469a98bafecac923 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 24 Nov 2010 18:50:30 -0500 Subject: typo fix --- CHANGES.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 547a7254a..3c6b8e010 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,8 +10,8 @@ Features templates now use idiomatic SQLAlchemy configuration in their respective ``.ini`` files and Python code. -- ``pyramid.testig.DummyRequest`` now has a class variable, ``query_string``, - which defaults to the empty string. +- ``pyramid.testing.DummyRequest`` now has a class variable, + ``query_string``, which defaults to the empty string. - Add support for json on GAE by catching NotImplementedError and importing simplejson from django.utils. -- cgit v1.2.3 From b64d22c7e5692e10434eec83f6e2c64a4fed6a9e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 24 Nov 2010 19:01:49 -0500 Subject: allow imports, directories, and default_filters to be lists in settings --- pyramid/mako_templating.py | 11 ++++++---- pyramid/tests/test_mako_templating.py | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index c9c041edb..3ef2868a5 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -71,7 +71,8 @@ def renderer_factory(info): if directories is None: raise ConfigurationError( 'Mako template used without a ``mako.directories`` setting') - directories = filter(None, directories.splitlines()) + if not hasattr(directories, '__iter__'): + directories = filter(None, directories.splitlines()) directories = [ abspath_from_resource_spec(d) for d in directories ] if module_directory is not None: module_directory = abspath_from_resource_spec(module_directory) @@ -79,9 +80,11 @@ def renderer_factory(info): dotted = DottedNameResolver(info.package) error_handler = dotted.maybe_resolve(error_handler) if default_filters is not None: - default_filters = filter(None, default_filters.splitlines()) - if imports: - imports = filter(None, imports.splitlines()) + if not hasattr(default_filters, '__iter__'): + default_filters = filter(None, default_filters.splitlines()) + if imports is not None: + if not hasattr(imports, '__iter__'): + imports = filter(None, imports.splitlines()) lookup = PkgResourceTemplateLookup(directories=directories, module_directory=module_directory, diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index cfb54b902..4b75e1a28 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -59,6 +59,19 @@ class Test_renderer_factory(Base, unittest.TestCase): lookup = self.config.registry.getUtility(IMakoLookup) self.assertEqual(lookup.directories, [self.templates_dir]*2) + def test_directories_list(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':['a', 'b']} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.directories, ['a', 'b']) + def test_with_module_directory_resource_spec(self): import os from pyramid.mako_templating import IMakoLookup @@ -135,6 +148,20 @@ class Test_renderer_factory(Base, unittest.TestCase): lookup = self.config.registry.getUtility(IMakoLookup) self.assertEqual(lookup.template_args['default_filters'], ['h', 'g']) + def test_with_default_filters_list(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.default_filters':['h', 'g']} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['default_filters'], ['h', 'g']) + def test_with_imports(self): from pyramid.mako_templating import IMakoLookup settings = {'mako.directories':self.templates_dir, @@ -149,6 +176,20 @@ class Test_renderer_factory(Base, unittest.TestCase): lookup = self.config.registry.getUtility(IMakoLookup) self.assertEqual(lookup.template_args['imports'], ['one', 'two']) + def test_with_imports_list(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.imports':['one', 'two']} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['imports'], ['one', 'two']) + def test_with_lookup(self): from pyramid.mako_templating import IMakoLookup lookup = dict() -- cgit v1.2.3 From 81e648ffded9caa9480c0053c11a0bc15afbc39a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 24 Nov 2010 23:07:58 -0500 Subject: - New boolean Mako settings variable ``mako.strict_undefined``. See `Mako Context Variables `_ for its meaning. - Depend on Mako 0.3.6+ (we now require the ``strict_undefined`` feature). --- CHANGES.txt | 10 ++++++++++ TODO.txt | 2 -- docs/narr/environment.rst | 18 ++++++++++++++++++ pyramid/mako_templating.py | 6 +++++- pyramid/tests/test_mako_templating.py | 31 ++++++++++++++++++++++++++++++- setup.py | 2 +- 6 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3c6b8e010..1675ab9fc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,16 @@ Features - The Mako renderer now accepts a resource specification for ``mako.module_directory``. +- New boolean Mako settings variable ``mako.strict_undefined``. See `Mako + Context Variables + `_ for + its meaning. + +Dependencies +------------ + +- Depend on Mako 0.3.6+ (we now require the ``strict_undefined`` feature). + Bug Fixes --------- diff --git a/TODO.txt b/TODO.txt index c5584d3cc..0033eb3b3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -20,8 +20,6 @@ Should-Have - Add docs for httpexceptions module for each webob.exc class that inherits from WSGIHTTPException. -- Create a ``docs`` directory for each paster template. - - Remove "BFG" from Pyramid-specific environ variables. - translationdir ZCML directive use of ``path_spec`` should maybe die. diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index ecf85e464..c3fe401ec 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -249,6 +249,24 @@ will be placed into the module level preamble of all generated Python modules. | | +-----------------------------+ + +Mako Strict Undefined ++++++++++++++++++++++ + +``true`` or ``false``, representing the "strict undefined" behavior of Mako +(see `Mako Context Variables +`_). By +default, this is ``false``. + ++-----------------------------+ +| Config File Setting Name | ++=============================+ +| ``mako.strict_undefined`` | +| | +| | +| | ++-----------------------------+ + Examples -------- diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index 3ef2868a5..a5f9b0f68 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -8,6 +8,7 @@ from pyramid.interfaces import ITemplateRenderer from pyramid.exceptions import ConfigurationError from pyramid.resource import resolve_resource_spec from pyramid.resource import abspath_from_resource_spec +from pyramid.settings import asbool from pyramid.util import DottedNameResolver from mako.lookup import TemplateLookup @@ -68,6 +69,7 @@ def renderer_factory(info): error_handler = settings.get('mako.error_handler', None) default_filters = settings.get('mako.default_filters', None) imports = settings.get('mako.imports', None) + strict_undefined = settings.get('mako.strict_undefined', 'false') if directories is None: raise ConfigurationError( 'Mako template used without a ``mako.directories`` setting') @@ -85,6 +87,7 @@ def renderer_factory(info): if imports is not None: if not hasattr(imports, '__iter__'): imports = filter(None, imports.splitlines()) + strict_undefined = asbool(strict_undefined) lookup = PkgResourceTemplateLookup(directories=directories, module_directory=module_directory, @@ -92,7 +95,8 @@ def renderer_factory(info): error_handler=error_handler, default_filters=default_filters, imports=imports, - filesystem_checks=reload_templates) + filesystem_checks=reload_templates, + strict_undefined=strict_undefined) registry_lock.acquire() try: registry.registerUtility(lookup, IMakoLookup) diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index 4b75e1a28..506dc18cd 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -190,6 +190,34 @@ class Test_renderer_factory(Base, unittest.TestCase): lookup = self.config.registry.getUtility(IMakoLookup) self.assertEqual(lookup.template_args['imports'], ['one', 'two']) + def test_with_strict_undefined_true(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.strict_undefined':'true'} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['strict_undefined'], True) + + def test_with_strict_undefined_false(self): + from pyramid.mako_templating import IMakoLookup + settings = {'mako.directories':self.templates_dir, + 'mako.strict_undefined':'false'} + info = DummyRendererInfo({ + 'name':'helloworld.mak', + 'package':None, + 'registry':self.config.registry, + 'settings':settings, + }) + self._callFUT(info) + lookup = self.config.registry.getUtility(IMakoLookup) + self.assertEqual(lookup.template_args['strict_undefined'], False) + def test_with_lookup(self): from pyramid.mako_templating import IMakoLookup lookup = dict() @@ -301,7 +329,8 @@ class TestIntegration(unittest.TestCase): def test_render_to_response_pkg_spec(self): from pyramid.renderers import render_to_response - result = render_to_response('pyramid.tests:fixtures/helloworld.mak', {'a':1}) + result = render_to_response('pyramid.tests:fixtures/helloworld.mak', + {'a':1}) self.assertEqual(result.ubody, u'\nHello föö\n') def test_render_with_abs_path(self): diff --git a/setup.py b/setup.py index c1939f73c..75f5b13dd 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ except IOError: install_requires=[ 'Chameleon >= 1.2.3', - 'Mako', + 'Mako >= 0.3.6', # strict_undefined 'Paste > 1.7', # temp version pin to prevent PyPi install failure :-( 'PasteDeploy', 'PasteScript', -- cgit v1.2.3 From 8add482cbd7353542b9bac61e8c13deecbe8825e Mon Sep 17 00:00:00 2001 From: Blaise Laflamme Date: Fri, 26 Nov 2010 15:26:36 -0500 Subject: Updated starter template with new theme --- .../starter/+package+/static/footerbg.png | Bin 0 -> 333 bytes .../starter/+package+/static/headerbg.png | Bin 0 -> 203 bytes .../starter/+package+/static/ie6.css | 8 ++++++ .../starter/+package+/static/logo.png | Bin 6641 -> 0 bytes .../starter/+package+/static/middlebg.png | Bin 0 -> 2797 bytes .../starter/+package+/static/pylons.css | 30 +++++++++++---------- .../starter/+package+/static/pyramid.png | Bin 0 -> 33055 bytes .../starter/+package+/static/transparent.gif | Bin 0 -> 49 bytes .../starter/+package+/templates/mytemplate.pt | 25 ++++++++--------- 9 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 pyramid/paster_templates/starter/+package+/static/footerbg.png create mode 100644 pyramid/paster_templates/starter/+package+/static/headerbg.png create mode 100644 pyramid/paster_templates/starter/+package+/static/ie6.css delete mode 100644 pyramid/paster_templates/starter/+package+/static/logo.png create mode 100644 pyramid/paster_templates/starter/+package+/static/middlebg.png create mode 100644 pyramid/paster_templates/starter/+package+/static/pyramid.png create mode 100644 pyramid/paster_templates/starter/+package+/static/transparent.gif diff --git a/pyramid/paster_templates/starter/+package+/static/footerbg.png b/pyramid/paster_templates/starter/+package+/static/footerbg.png new file mode 100644 index 000000000..1fbc873da Binary files /dev/null and b/pyramid/paster_templates/starter/+package+/static/footerbg.png differ diff --git a/pyramid/paster_templates/starter/+package+/static/headerbg.png b/pyramid/paster_templates/starter/+package+/static/headerbg.png new file mode 100644 index 000000000..0596f2020 Binary files /dev/null and b/pyramid/paster_templates/starter/+package+/static/headerbg.png differ diff --git a/pyramid/paster_templates/starter/+package+/static/ie6.css b/pyramid/paster_templates/starter/+package+/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/pyramid/paster_templates/starter/+package+/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/pyramid/paster_templates/starter/+package+/static/logo.png b/pyramid/paster_templates/starter/+package+/static/logo.png deleted file mode 100644 index 88f5d9865..000000000 Binary files a/pyramid/paster_templates/starter/+package+/static/logo.png and /dev/null differ diff --git a/pyramid/paster_templates/starter/+package+/static/middlebg.png b/pyramid/paster_templates/starter/+package+/static/middlebg.png new file mode 100644 index 000000000..2369cfb7d Binary files /dev/null and b/pyramid/paster_templates/starter/+package+/static/middlebg.png differ diff --git a/pyramid/paster_templates/starter/+package+/static/pylons.css b/pyramid/paster_templates/starter/+package+/static/pylons.css index c153be07f..ba73a28f3 100644 --- a/pyramid/paster_templates/starter/+package+/static/pylons.css +++ b/pyramid/paster_templates/starter/+package+/static/pylons.css @@ -42,23 +42,25 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;} -#wrap {min-height: 100%;} -#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;} -#header{background-color:#e88f00;top:0;font-size:14px;} -#footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;} -.header,.footer{width:700px;margin-right:auto;margin-left:auto;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} #top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top{color:#000000;height:230px;/*border-bottom:1px solid #f19e00;*/ +background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;/*border-top:5px solid #000000;*/} +#bottom{color:#222;background-color:#ffffff;} +.top,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#left{width:350px;float:left;padding-right:25px;} +#right{width:350px;float:right;padding-left:25px;} .align-left{text-align:left;} .align-right{text-align:right;} .align-center{text-align:center;} @@ -67,7 +69,7 @@ ul.links li{list-style-type:none;font-size:14px;} form{border-style:none;} fieldset{border-style:none;} input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} -input[type=text]{} +input[type=text]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/pyramid/paster_templates/starter/+package+/static/pyramid.png b/pyramid/paster_templates/starter/+package+/static/pyramid.png new file mode 100644 index 000000000..347e05549 Binary files /dev/null and b/pyramid/paster_templates/starter/+package+/static/pyramid.png differ diff --git a/pyramid/paster_templates/starter/+package+/static/transparent.gif b/pyramid/paster_templates/starter/+package+/static/transparent.gif new file mode 100644 index 000000000..0341802e5 Binary files /dev/null and b/pyramid/paster_templates/starter/+package+/static/transparent.gif differ diff --git a/pyramid/paster_templates/starter/+package+/templates/mytemplate.pt b/pyramid/paster_templates/starter/+package+/templates/mytemplate.pt index 6ad23d44f..02fc00eeb 100644 --- a/pyramid/paster_templates/starter/+package+/templates/mytemplate.pt +++ b/pyramid/paster_templates/starter/+package+/templates/mytemplate.pt @@ -7,21 +7,21 @@ + -
-
- +
pyramid
+
+
+
+

Welcome to ${project}, an application generated by
the Pyramid web application development framework. @@ -31,21 +31,18 @@

-

Search Pyramid documentation

+

Search documentation

- +