From 661ea7644a8c725a0f03df01c1618a592e712e1a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 4 Jul 2011 03:12:19 -0400 Subject: partial conversion to eager response resolving --- pyramid/config.py | 58 +++++++++++++++++++++++---------------- pyramid/router.py | 11 ++------ pyramid/tests/test_config.py | 18 ++++++------ pyramid/tests/test_integration.py | 2 +- pyramid/tests/test_view.py | 13 --------- pyramid/view.py | 6 ---- 6 files changed, 48 insertions(+), 60 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index be0e425c8..493a37f3b 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2931,8 +2931,23 @@ class ViewDeriver(object): self.owrapped_view( self.decorated_view( self.http_cached_view( - self.rendered_view( - self.mapped_view(view))))))))) + self.response_resolved_view( + self.rendered_view( + self.mapped_view(view)))))))))) + + @wraps_view + def response_resolved_view(self, view): + registry = self.registry + def wrapper(context, request): + result = view(context, request) + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + if response is None: + raise ValueError( + 'Could not convert view return value "%s" into a ' + 'response object' % (result,)) + return response + return wrapper @wraps_view def mapped_view(self, view): @@ -2970,11 +2985,12 @@ class ViewDeriver(object): @wraps_view def http_cached_view(self, view): seconds = self.kw.get('http_cache') - options = {} if seconds is None: return view + options = {} + if isinstance(seconds, (tuple, list)): try: seconds, options = seconds @@ -2985,9 +3001,7 @@ class ViewDeriver(object): def wrapper(context, request): response = view(context, request) - cache_expires = getattr(response, 'cache_expires', None) - if cache_expires is not None: - cache_expires(seconds, **options) + response.cache_expires(seconds, **options) return response return wrapper @@ -3108,24 +3122,22 @@ class ViewDeriver(object): def _rendered_view(context, request): renderer = static_renderer - result = wrapped_view(context, request) + response = wrapped_view(context, request) registry = self.kw['registry'] - response = registry.queryAdapterOrSelf(result, IResponse) - if response is None: - attrs = getattr(request, '__dict__', {}) - if 'override_renderer' in attrs: - # renderer overridden by newrequest event or other - renderer_name = attrs.pop('override_renderer') - renderer = RendererHelper(name=renderer_name, - package=self.kw.get('package'), - registry = registry) - if '__view__' in attrs: - view_inst = attrs.pop('__view__') - else: - view_inst = getattr(wrapped_view, '__original_view__', - wrapped_view) - response = renderer.render_view(request, result, view_inst, - context) + attrs = getattr(request, '__dict__', {}) + if 'override_renderer' in attrs: + # renderer overridden by newrequest event or other + renderer_name = attrs.pop('override_renderer') + renderer = RendererHelper(name=renderer_name, + package=self.kw.get('package'), + registry = registry) + if '__view__' in attrs: + view_inst = attrs.pop('__view__') + else: + view_inst = getattr(wrapped_view, '__original_view__', + wrapped_view) + response = renderer.render_view(request, response, view_inst, + context) return response return _rendered_view diff --git a/pyramid/router.py b/pyramid/router.py index 458237a8c..d011b1245 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -159,7 +159,7 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: - result = view_callable(context, request) + response = view_callable(context, request) # handle exceptions raised during root finding and view-exec except Exception, why: @@ -173,14 +173,7 @@ class Router(object): if view_callable is None: raise - result = view_callable(why, request) - - # process the response - response = registry.queryAdapterOrSelf(result, IResponse) - if response is None: - raise ValueError( - 'Could not convert view return value "%s" into a ' - 'response object' % (result,)) + response = view_callable(why, request) has_listeners and notify(NewResponse(request, response)) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index fa1ad2b88..0b178d22e 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3972,7 +3972,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(predicates, [True, True]) def test_with_wrapper_viewname(self): - from webob import Response + from pyramid.response import Response from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier inner_response = Response('OK') @@ -3996,7 +3996,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(response.body, 'outer OK') def test_with_wrapper_viewname_notfound(self): - from webob import Response + from pyramid.response import Response inner_response = Response('OK') def inner_view(context, request): return inner_response @@ -4198,7 +4198,7 @@ class TestViewDeriver(unittest.TestCase): def test_http_cached_view_integer(self): import datetime - from webob import Response + from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response @@ -4218,7 +4218,7 @@ class TestViewDeriver(unittest.TestCase): def test_http_cached_view_timedelta(self): import datetime - from webob import Response + from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response @@ -4238,7 +4238,7 @@ class TestViewDeriver(unittest.TestCase): def test_http_cached_view_tuple(self): import datetime - from webob import Response + from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response @@ -4257,7 +4257,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(headers['Cache-Control'], 'max-age=3600, public') def test_http_cached_view_tuple_seconds_None(self): - from webob import Response + from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response @@ -4274,8 +4274,10 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(headers['Cache-Control'], 'public') def test_http_cached_view_nonresponse_object_returned_downstream(self): + from pyramid.response import Response + response = Response() def inner_view(context, request): - return None + return response deriver = self._makeOne(http_cache=3600) result = deriver(inner_view) self.assertFalse(result is inner_view) @@ -4283,7 +4285,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() result = result(None, request) - self.assertEqual(result, None) # doesn't blow up + self.assertEqual(result, response) # doesn't blow up def test_http_cached_view_bad_tuple(self): from pyramid.exceptions import ConfigurationError diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index dd77d3aec..0ef1e1631 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -39,7 +39,7 @@ class WGSIAppPlusViewConfigTests(unittest.TestCase): reg = config.registry view = reg.adapters.lookup( (IViewClassifier, IRequest, INothing), IView, name='') - self.assertEqual(view, wsgiapptest) + self.assertEqual(view.__original_view__, wsgiapptest) here = os.path.dirname(__file__) staticapp = static(os.path.join(here, 'fixtures')) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index ce00454fc..6bb3b01a0 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -121,19 +121,6 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): secure=True) self.assertEqual(iterable, ()) - def test_call_view_returns_iresponse_adaptable(self): - from pyramid.response import Response - request = self._makeRequest() - context = self._makeContext() - view = make_view('123') - self._registerView(request.registry, view, 'registered') - def str_response(s): - return Response(s) - request.registry.registerAdapter(str_response, (str,), IResponse) - iterable = self._callFUT(context, request, name='registered', - secure=True) - self.assertEqual(iterable, ['123']) - def test_call_view_registered_insecure_no_call_permissive(self): context = self._makeContext() request = self._makeRequest() diff --git a/pyramid/view.py b/pyramid/view.py index ea20a19c2..ef7352301 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -4,7 +4,6 @@ import venusian from zope.interface import providedBy from zope.deprecation import deprecated -from pyramid.interfaces import IResponse from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier @@ -101,11 +100,6 @@ def render_view_to_iterable(context, request, name='', secure=True): response = render_view_to_response(context, request, name, secure) if response is None: return None - try: - reg = request.registry - except AttributeError: - reg = get_current_registry() - response = reg.queryAdapterOrSelf(response, IResponse) return response.app_iter def render_view(context, request, name='', secure=True): -- cgit v1.2.3 From b954816ab2ca43ccd4bc2eb53130b81ad0512996 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 5 Jul 2011 22:41:48 -0400 Subject: garden --- TODO.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO.txt b/TODO.txt index fc64163e1..d074b6dac 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,6 +6,12 @@ Must-Have - Github issues fixes. +- add_route discriminator wrong + +- tutorial models.initialize_sql doesn't match scaffold + (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in + tutorial) + Should-Have ----------- -- cgit v1.2.3 From 22a2b3fb6d917b425aa40dee9800f6841de6de64 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 6 Jul 2011 00:32:07 -0400 Subject: garden --- TODO.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.txt b/TODO.txt index d074b6dac..d594a4ea3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -12,6 +12,8 @@ Must-Have (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in tutorial) +- Allow views to override http_cache headers. + Should-Have ----------- -- cgit v1.2.3 From c5ab0dd7e83bfedd70bb86a18600b7f36949d62c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 6 Jul 2011 00:37:40 -0400 Subject: garden --- TODO.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.txt b/TODO.txt index d594a4ea3..ab0e6da41 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,6 +14,8 @@ Must-Have - Allow views to override http_cache headers. +- request.JSON dictionary? + Should-Have ----------- -- cgit v1.2.3 From 6b01adc58935e95908c42bc982803b20ceaf60e2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 5 Jul 2011 23:52:39 -0500 Subject: Updated paster commands to use the ini#section uri spec. --- pyramid/paster.py | 79 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index b1f0a6c8b..f670ae618 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -15,12 +15,22 @@ zope.deprecation.deprecated( 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'), ) -def get_app(config_file, name, loadapp=loadapp): +def get_app(config_file, name=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``""" - config_name = 'config:%s' % config_file + config file ``config_file``. + + If the ``name`` is None, this will attempt to parse the name from + the ``config_file`` string expecting the format ``ini_path#name``. + If no name is found, the name will default to "main".""" + if '#' in config_file: + path, section = config_file.split('#', 1) + else: + path, section = config_file, 'main' + if name: + section = name + config_name = 'config:%s' % path here_dir = os.getcwd() - app = loadapp(config_name, name=name, relative_to=here_dir) + app = loadapp(config_name, name=section, relative_to=here_dir) return app _marker = object() @@ -42,17 +52,15 @@ class PCommand(Command): class PShellCommand(PCommand): """Open an interactive shell with a :app:`Pyramid` app loaded. - This command accepts two positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts one positional argument: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. Example:: - $ paster pshell myapp.ini main + $ paster pshell myapp.ini#main .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -62,8 +70,8 @@ class PShellCommand(PCommand): """ summary = "Open an interactive shell with a Pyramid application loaded" - min_args = 2 - max_args = 2 + min_args = 1 + max_args = 1 parser = Command.standard_parser(simulate=True) parser.add_option('-d', '--disable-ipython', @@ -81,9 +89,10 @@ class PShellCommand(PCommand): cprt =('Type "help" for more information. "root" is the Pyramid app ' 'root object, "registry" is the Pyramid registry object.') banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - config_file, section_name = self.args + app_spec = self.args[0] + config_file = app_spec.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app = self.get_app(app_spec, loadapp=self.loadapp[0]) root, closer = self.get_root(app) shell_globals = {'root':root, 'registry':app.registry} @@ -108,17 +117,15 @@ class PRoutesCommand(PCommand): route, the pattern of the route, and the view callable which will be invoked when the route is matched. - This command accepts two positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts one positional argument: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. Example:: - $ paster proutes myapp.ini main + $ paster proutes myapp.ini#main .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -126,8 +133,8 @@ class PRoutesCommand(PCommand): command will almost certainly fail. """ summary = "Print all URL dispatch routes related to a Pyramid application" - min_args = 2 - max_args = 2 + min_args = 1 + max_args = 1 stdout = sys.stdout parser = Command.standard_parser(simulate=True) @@ -146,8 +153,8 @@ class PRoutesCommand(PCommand): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from zope.interface import Interface - config_file, section_name = self.args - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app_spec = self.args[0] + app = self.get_app(app_spec, loadapp=self.loadapp[0]) registry = app.registry mapper = self._get_mapper(app) if mapper is not None: @@ -179,19 +186,17 @@ class PViewsCommand(PCommand): each route+predicate set, print each view that might match and its predicates. - This command accepts three positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts two positional arguments: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. ``url`` -- specifies the URL that will be used to find matching views. Example:: - $ paster proutes myapp.ini main url + $ paster proutes myapp.ini#main url .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -199,8 +204,8 @@ class PViewsCommand(PCommand): command will almost certainly fail. """ summary = "Print all views in an application that might match a URL" - min_args = 3 - max_args = 3 + min_args = 2 + max_args = 2 stdout = sys.stdout parser = Command.standard_parser(simulate=True) @@ -395,10 +400,10 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - config_file, section_name, url = self.args + app_spec, url = self.args[0] if not url.startswith('/'): url = '/%s' % url - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app = self.get_app(app_spec, loadapp=self.loadapp[0]) registry = app.registry view = self._find_view(url, registry) self.out('') -- cgit v1.2.3 From 828384c14b07aa4f50a70f44f789ce4b1b1e34f6 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 01:32:52 -0500 Subject: Added [pshell] support to INI files and the ability to load generic wsgi apps. Generic WSGI apps can now be loaded without exposing the 'registry' and 'root' objects. When an app is loaded, extra variables will be exposed to the environment from a special [pshell] section of the INI file. These variables are of the format "name = asset.path.to.object". --- pyramid/paster.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index f670ae618..9683da043 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,13 +1,16 @@ import os +import re import sys from code import interact import zope.deprecation from paste.deploy import loadapp +from paste.script.command import BadCommand from paste.script.command import Command from pyramid.scripting import get_root +from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat zope.deprecation.deprecated( @@ -79,6 +82,45 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") + _pshell_section_re = re.compile(r'^\s*\[\s*pshell\s*\]\s*$') + _section_re = re.compile(r'^\s*\[') + + def pshell_file_config(self, filename): + vars = { + 'here': os.path.dirname(filename), + '__file__': filename, + } + f = open(filename) + lines = f.readlines() + f.close() + lineno = 1 + # find the pshell section + while lines: + if self._pshell_section_re.search(lines[0]): + lines.pop(0) + break + lines.pop(0) + lineno += 1 + # parse pshell section for key/value pairs + resolver = DottedNameResolver(None) + self.loaded_objects = {} + self.object_help = {} + for line in lines: + lineno += 1 + line = line.strip() + if not line or line.startswith('#'): + continue + if self._section_re.search(line): + break + if '=' not in line: + raise BadCommand('Missing = in %s at %s: %r' + % (filename, lineno, line)) + name, value = line.split('=', 1) + name = name.strip() + value = value.strip() % vars + self.loaded_objects[name] = resolver.maybe_resolve(value) + self.object_help[name] = value + def command(self, IPShell=_marker): # IPShell passed to command method is for testing purposes if IPShell is _marker: # pragma: no cover @@ -86,15 +128,58 @@ class PShellCommand(PCommand): from IPython.Shell import IPShell except ImportError: IPShell = None - cprt =('Type "help" for more information. "root" is the Pyramid app ' - 'root object, "registry" is the Pyramid registry object.') + cprt =('Type "help" for more information.') banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) app_spec = self.args[0] config_file = app_spec.split('#', 1)[0] self.logging_file_config(config_file) app = self.get_app(app_spec, loadapp=self.loadapp[0]) - root, closer = self.get_root(app) - shell_globals = {'root':root, 'registry':app.registry} + + # load default globals + shell_globals = { + 'app': app, + } + default_variables = {'app': 'The WSGI Application'} + if hasattr(app, 'registry'): + root, closer = self.get_root(app) + shell_globals.update({'root':root, 'registry':app.registry}) + default_variables.update({ + 'root': 'The root of the default resource tree.', + 'registry': 'The Pyramid registry object.', + 'settings': 'The Pyramid settings object.', + }) + warning = '' + else: + # warn the user that this isn't actually the Pyramid app + warning = """\n +WARNING: You have loaded a generic WSGI application, therefore the +"root" and "registry" are not available. To correct this, run "pshell" +again and specify the INI section containing your Pyramid application.""" + closer = lambda: None + + # load the pshell section of the ini file + self.pshell_file_config(config_file) + shell_globals.update(self.loaded_objects) + + # eliminate duplicates from default_variables + for k in self.loaded_objects: + if k in default_variables: + del default_variables[k] + + # append the loaded variables + if default_variables: + banner += '\n\nDefault Variables:' + for var, txt in default_variables.iteritems(): + banner += '\n %-12s %s' % (var, txt) + + if self.object_help: + banner += '\n\nCustom Variables:' + for var in sorted(self.object_help.keys()): + banner += '\n %-12s %s' % (var, self.object_help[var]) + + # append the warning + banner += warning + banner += '\n' if (IPShell is None) or self.options.disable_ipython: try: -- cgit v1.2.3 From 7b1a206b06610dc482219d12bcd00e1d47fe4517 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:27:21 -0500 Subject: Updated paster tests to use the ini_file#section syntax. --- pyramid/tests/test_paster.py | 66 +++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index cf0b38a80..2bae6aba3 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -15,7 +15,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -39,7 +39,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = True @@ -62,7 +62,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -92,7 +92,7 @@ class TestPShellCommand(unittest.TestCase): interact = DummyInteractor() app = DummyApp() command.interact = (interact,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -121,7 +121,7 @@ class TestPShellCommand(unittest.TestCase): apps.append(app) return root, lambda *arg: None command.get_root =get_root - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -152,7 +152,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -165,7 +165,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -180,7 +180,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -205,7 +205,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -235,7 +235,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -268,7 +268,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -511,7 +511,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -528,7 +528,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', 'a') + command.args = ('/foo/bar/myapp.ini#myapp', 'a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -546,7 +546,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -567,7 +567,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -588,7 +588,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -612,7 +612,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -635,7 +635,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -665,7 +665,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -690,7 +690,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -719,7 +719,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -743,7 +743,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -770,7 +770,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -798,6 +798,26 @@ class TestGetApp(unittest.TestCase): self.assertEqual(loadapp.section_name, 'myapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) + + def test_it_with_hash(self): + import os + app = DummyApp() + loadapp = DummyLoadApp(app) + result = self._callFUT('/foo/bar/myapp.ini#myapp', None, loadapp) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertEqual(loadapp.relative_to, os.getcwd()) + self.assertEqual(result, app) + + def test_it_with_hash_and_name_override(self): + import os + app = DummyApp() + loadapp = DummyLoadApp(app) + result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp', loadapp) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'yourapp') + self.assertEqual(loadapp.relative_to, os.getcwd()) + self.assertEqual(result, app) -- cgit v1.2.3 From 26d135bae9aa1b24774a76053b22e9202e0421bb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:28:09 -0500 Subject: Fixed a small bug in pviews initialization. --- pyramid/paster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 9683da043..2e732bce4 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -485,7 +485,7 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - app_spec, url = self.args[0] + app_spec, url = self.args if not url.startswith('/'): url = '/%s' % url app = self.get_app(app_spec, loadapp=self.loadapp[0]) -- cgit v1.2.3 From 648a9ee498dc1a02ab79bc4e29dcb1d2b1697e45 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:28:24 -0500 Subject: Replaced the manual parsing with python's ConfigParser. --- pyramid/paster.py | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 2e732bce4..0360a57bb 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -2,11 +2,11 @@ import os import re import sys from code import interact +from ConfigParser import ConfigParser import zope.deprecation from paste.deploy import loadapp -from paste.script.command import BadCommand from paste.script.command import Command from pyramid.scripting import get_root @@ -82,44 +82,17 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") - _pshell_section_re = re.compile(r'^\s*\[\s*pshell\s*\]\s*$') - _section_re = re.compile(r'^\s*\[') + ConfigParser = ConfigParser # testing def pshell_file_config(self, filename): - vars = { - 'here': os.path.dirname(filename), - '__file__': filename, - } - f = open(filename) - lines = f.readlines() - f.close() - lineno = 1 - # find the pshell section - while lines: - if self._pshell_section_re.search(lines[0]): - lines.pop(0) - break - lines.pop(0) - lineno += 1 - # parse pshell section for key/value pairs resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} - for line in lines: - lineno += 1 - line = line.strip() - if not line or line.startswith('#'): - continue - if self._section_re.search(line): - break - if '=' not in line: - raise BadCommand('Missing = in %s at %s: %r' - % (filename, lineno, line)) - name, value = line.split('=', 1) - name = name.strip() - value = value.strip() % vars - self.loaded_objects[name] = resolver.maybe_resolve(value) - self.object_help[name] = value + config = ConfigParser() + config.read(filename) + for k, v in config.items('pshell'): + self.loaded_objects[k] = resolver.maybe_resolve(v) + self.object_help[k] = v def command(self, IPShell=_marker): # IPShell passed to command method is for testing purposes -- cgit v1.2.3 From e6d8e5394aeb1111d955bc21712c93bcb26ffcc0 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 03:25:52 -0500 Subject: Added tests for the new pshell. --- pyramid/paster.py | 17 +++-- pyramid/tests/test_paster.py | 165 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 155 insertions(+), 27 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 0360a57bb..eabd12a1b 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,8 +1,8 @@ +import ConfigParser import os import re import sys from code import interact -from ConfigParser import ConfigParser import zope.deprecation @@ -23,7 +23,7 @@ def get_app(config_file, name=None, loadapp=loadapp): config file ``config_file``. If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_path#name``. + the ``config_file`` string expecting the format ``ini_file#name``. If no name is found, the name will default to "main".""" if '#' in config_file: path, section = config_file.split('#', 1) @@ -82,15 +82,19 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") - ConfigParser = ConfigParser # testing + ConfigParser = ConfigParser.ConfigParser # testing def pshell_file_config(self, filename): resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} - config = ConfigParser() + config = self.ConfigParser() config.read(filename) - for k, v in config.items('pshell'): + try: + items = config.items('pshell') + except ConfigParser.NoSectionError: + return + for k, v in items: self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v @@ -115,7 +119,8 @@ class PShellCommand(PCommand): default_variables = {'app': 'The WSGI Application'} if hasattr(app, 'registry'): root, closer = self.get_root(app) - shell_globals.update({'root':root, 'registry':app.registry}) + shell_globals.update({'root':root, 'registry':app.registry, + 'settings': app.registry.settings}) default_variables.update({ 'root': 'The root of the default resource tree.', 'registry': 'The Pyramid registry object.', diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 2bae6aba3..e7a3b7507 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -15,7 +15,8 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -27,8 +28,10 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -39,7 +42,8 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = True @@ -51,8 +55,10 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -61,8 +67,9 @@ class TestPShellCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser({}) dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -75,7 +82,9 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) self.assertEqual(dummy_shell_factory.shell.local_ns, - {'root':dummy_root, 'registry':dummy_registry}) + {'app':app, 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertEqual(dummy_shell_factory.shell.global_ns, {}) self.assertTrue('\n\n' in dummy_shell_factory.shell.IP.BANNER) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -92,7 +101,8 @@ class TestPShellCommand(unittest.TestCase): interact = DummyInteractor() app = DummyApp() command.interact = (interact,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -101,11 +111,13 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app': app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) - self.assertEqual(apped, [(('/foo/bar/myapp.ini', 'myapp'), + self.assertEqual(apped, [(('/foo/bar/myapp.ini#myapp',), {'loadapp': loadapp})]) def test_command_get_root_hookable(self): @@ -115,13 +127,14 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser({}) root = Dummy() apps = [] def get_root(app): apps.append(app) return root, lambda *arg: None command.get_root =get_root - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -130,11 +143,108 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(loadapp.section_name, 'myapp') self.assertTrue(loadapp.relative_to) self.assertEqual(len(app.threadlocal_manager.pushed), 0) - self.assertEqual(interact.local, {'root':root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(apps, [app]) + def test_command_loads_custom_items(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + model = Dummy() + command.ConfigParser = makeDummyConfigParser([('m', model)]) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'].registry, dummy_registry) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings, + 'm': model}) + self.assertTrue(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_command_no_custom_section(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser(None) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'].registry, dummy_registry) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) + self.assertTrue(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_command_custom_section_override(self): + command = self._makeOne() + interact = DummyInteractor() + app = Dummy() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + model = Dummy() + command.ConfigParser = makeDummyConfigParser([('app', model)]) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(interact.local, {'app':model}) + self.assertTrue(interact.banner) + + def test_command_generic_wsgi_app(self): + command = self._makeOne() + interact = DummyInteractor() + app = Dummy() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser(None) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(interact.local, {'app':app}) + self.assertTrue(interact.banner) + class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PRoutesCommand @@ -152,7 +262,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -165,7 +275,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -180,7 +290,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -205,7 +315,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -235,7 +345,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -268,7 +378,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -844,6 +954,7 @@ class DummyIPShell(object): dummy_root = Dummy() class DummyRegistry(object): + settings = {} def queryUtility(self, iface, default=None, name=''): return default @@ -925,3 +1036,15 @@ class DummyMultiView(object): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs +def makeDummyConfigParser(items): + class DummyConfigParser(object): + def read(self, filename): + self.filename = filename + + def items(self, section): + self.section = section + if items is None: + from ConfigParser import NoSectionError + raise NoSectionError, section + return items + return DummyConfigParser -- cgit v1.2.3 From fca1efe07ac46b2817f30299bbebbc2031cce339 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 6 Jul 2011 14:57:59 -0400 Subject: - The ``config.scan`` method has grown a ``**kw`` argument. ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object created by Pyramid. (See the Venusian documentation for more information about ``Scanner``). --- CHANGES.txt | 5 +++++ docs/whatsnew-1.1.rst | 5 +++++ pyramid/config.py | 20 ++++++++++++++++++-- pyramid/tests/test_config.py | 5 +++++ pyramid/tests/venusianapp/__init__.py | 14 ++++++++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 pyramid/tests/venusianapp/__init__.py diff --git a/CHANGES.txt b/CHANGES.txt index 0eb02baad..041ff0bb7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Next release Features -------- +- The ``config.scan`` method has grown a ``**kw`` argument. ``kw`` argument + represents a set of keyword arguments to pass to the Venusian ``Scanner`` + object created by Pyramid. (See the Venusian documentation for more + information about ``Scanner``). + - New request attribute: ``json``. If the request's ``content_type`` is ``application/json``, this attribute will contain the JSON-decoded variant of the request body. If the request's ``content_type`` is not diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index fdf3b1c74..a6bb8e99d 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -94,6 +94,11 @@ Default HTTP Exception View Minor Feature Additions ----------------------- +- The :meth:`pyramid.config.Configurator.scan` method has grown a ``**kw`` + argument. ``kw`` argument represents a set of keyword arguments to pass to + the Venusian ``Scanner`` object created by Pyramid. (See the + :term:`Venusian` documentation for more information about ``Scanner``). + - New request attribute: ``json``. If the request's ``content_type`` is ``application/json``, this attribute will contain the JSON-decoded variant of the request body. If the request's ``content_type`` is not diff --git a/pyramid/config.py b/pyramid/config.py index 3ad872e27..2a1a179e5 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1950,7 +1950,7 @@ class Configurator(object): return mapper # this is *not* an action method (uses caller_package) - def scan(self, package=None, categories=None): + def scan(self, package=None, categories=None, **kw): """Scan a Python package and any of its subpackages for objects marked with :term:`configuration decoration` such as :class:`pyramid.view.view_config`. Any decorated object found will @@ -1970,12 +1970,28 @@ class Configurator(object): :class:`pyramid.view.view_config`. See the :term:`Venusian` documentation for more information about limiting a scan by using an explicit set of categories. + + To perform a ``scan``, Pyramid creates a Venusian ``Scanner`` object. + The ``kw`` argument represents a set of keyword arguments to pass to + the Venusian ``Scanner`` object's constructor. See the + :term:`venusian` documentation (its ``Scanner`` class) for more + information. By default, the only keyword arguments passed to the + Scanner constructor are ``{'config':self}`` where ``self`` is this + configurator object. This services the requirement of all built-in + Pyramid decorators, but extension systems may require additional + arguments. Providing this argument is not often necessary; it's an + advanced usage. + + .. note:: the ``**kw`` argument is new in Pyramid 1.1 """ package = self.maybe_dotted(package) if package is None: # pragma: no cover package = caller_package() - scanner = self.venusian.Scanner(config=self) + scankw = {'config':self} + scankw.update(kw) + + scanner = self.venusian.Scanner(**scankw) scanner.scan(package, categories=categories) @action_method diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index fa1ad2b88..45b5b25f1 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -2848,6 +2848,11 @@ class ConfiguratorTests(unittest.TestCase): result = render_view_to_response(ctx, req, '') self.assertEqual(result, 'grokked') + def test_scan_integration_with_extra_kw(self): + config = self._makeOne(autocommit=True) + config.scan('pyramid.tests.venusianapp', a=1) + self.assertEqual(config.a, 1) + def test_testing_securitypolicy(self): from pyramid.testing import DummySecurityPolicy config = self._makeOne(autocommit=True) diff --git a/pyramid/tests/venusianapp/__init__.py b/pyramid/tests/venusianapp/__init__.py new file mode 100644 index 000000000..ce5e07238 --- /dev/null +++ b/pyramid/tests/venusianapp/__init__.py @@ -0,0 +1,14 @@ +import venusian + +def foo(wrapped): + def bar(scanner, name, wrapped): + scanner.config.a = scanner.a + venusian.attach(wrapped, bar) + return wrapped + +@foo +def hello(): + pass + +hello() # appease coverage + -- cgit v1.2.3 From 9d943e9de325a8ba1ae173882d8722010ca6459d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 6 Jul 2011 15:01:10 -0400 Subject: typo --- pyramid/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 2a1a179e5..dbc68a190 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1975,12 +1975,12 @@ class Configurator(object): The ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object's constructor. See the :term:`venusian` documentation (its ``Scanner`` class) for more - information. By default, the only keyword arguments passed to the - Scanner constructor are ``{'config':self}`` where ``self`` is this - configurator object. This services the requirement of all built-in - Pyramid decorators, but extension systems may require additional - arguments. Providing this argument is not often necessary; it's an - advanced usage. + information about the constructor. By default, the only keyword + arguments passed to the Scanner constructor are ``{'config':self}`` + where ``self`` is this configurator object. This services the + requirement of all built-in Pyramid decorators, but extension systems + may require additional arguments. Providing this argument is not + often necessary; it's an advanced usage. .. note:: the ``**kw`` argument is new in Pyramid 1.1 """ -- cgit v1.2.3 From 32859f733794556e339ea2779b03894e570e0336 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 7 Jul 2011 01:24:37 -0400 Subject: fix a few tests broken by refactoring --- pyramid/config.py | 34 +++++++++++++++++++--------------- pyramid/tests/restbugapp/views.py | 2 +- pyramid/tests/test_router.py | 21 +++++++++++++-------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 493a37f3b..184c0c6e8 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -3122,22 +3122,26 @@ class ViewDeriver(object): def _rendered_view(context, request): renderer = static_renderer - response = wrapped_view(context, request) + result = wrapped_view(context, request) registry = self.kw['registry'] - attrs = getattr(request, '__dict__', {}) - if 'override_renderer' in attrs: - # renderer overridden by newrequest event or other - renderer_name = attrs.pop('override_renderer') - renderer = RendererHelper(name=renderer_name, - package=self.kw.get('package'), - registry = registry) - if '__view__' in attrs: - view_inst = attrs.pop('__view__') - else: - view_inst = getattr(wrapped_view, '__original_view__', - wrapped_view) - response = renderer.render_view(request, response, view_inst, - context) + # this must adapt, it can't do a simple interface check + # (webob responses) + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + attrs = getattr(request, '__dict__', {}) + if 'override_renderer' in attrs: + # renderer overridden by newrequest event or other + renderer_name = attrs.pop('override_renderer') + renderer = RendererHelper(name=renderer_name, + package=self.kw.get('package'), + registry = registry) + if '__view__' in attrs: + view_inst = attrs.pop('__view__') + else: + view_inst = getattr(wrapped_view, '__original_view__', + wrapped_view) + response = renderer.render_view(request, result, view_inst, + context) return response return _rendered_view diff --git a/pyramid/tests/restbugapp/views.py b/pyramid/tests/restbugapp/views.py index b94851099..2ace59fa9 100644 --- a/pyramid/tests/restbugapp/views.py +++ b/pyramid/tests/restbugapp/views.py @@ -1,4 +1,4 @@ -from webob import Response +from pyramid.response import Response class BaseRESTView(object): def __init__(self, context, request): diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 5fd2cf01e..af311cfc2 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -4,9 +4,8 @@ from pyramid import testing class TestRouter(unittest.TestCase): def setUp(self): - testing.setUp() - from pyramid.threadlocal import get_current_registry - self.registry = get_current_registry() + self.config = testing.setUp() + self.registry = self.config.registry def tearDown(self): testing.tearDown() @@ -242,7 +241,8 @@ class TestRouter(unittest.TestCase): self._registerTraverserFactory(context) environ = self._makeEnviron() view = DummyView('abc') - self._registerView(view, '', IViewClassifier, None, None) + self._registerView(self.config.derive_view(view), '', IViewClassifier, + None, None) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) @@ -255,7 +255,8 @@ class TestRouter(unittest.TestCase): self._registerTraverserFactory(context) environ = self._makeEnviron() view = DummyView('abc') - self._registerView(view, '', IViewClassifier, None, None) + self._registerView(self.config.derive_view(view), '', + IViewClassifier, None, None) router = self._makeOne() start_response = DummyStartResponse() def make_response(s): @@ -273,7 +274,8 @@ class TestRouter(unittest.TestCase): response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() - self._registerView(view, '', IViewClassifier, None, None) + self._registerView(self.config.derive_view(view), '', + IViewClassifier, None, None) self._registerRootFactory(context) router = self._makeOne() start_response = DummyStartResponse() @@ -856,9 +858,12 @@ class TestRouter(unittest.TestCase): environ = self._makeEnviron() response = DummyResponse() view = DummyView(response, raise_exception=RuntimeError) - self._registerView(view, '', IViewClassifier, IRequest, None) + + self._registerView(self.config.derive_view(view), '', + IViewClassifier, IRequest, None) exception_view = DummyView(None) - self._registerView(exception_view, '', IExceptionViewClassifier, + self._registerView(self.config.derive_view(exception_view), '', + IExceptionViewClassifier, IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() -- cgit v1.2.3 From e8561f919548e2fe17f82d98e2a13e1e4e85bf40 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 7 Jul 2011 01:57:18 -0500 Subject: Added/updated documentation for the new interactive shell. --- docs/api/paster.rst | 7 ++-- docs/narr/project.rst | 96 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 9ecfa3d9c..6668f3c77 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -5,9 +5,12 @@ .. module:: pyramid.paster -.. function:: get_app(config_file, name) +.. function:: get_app(config_file, name=None) Return the WSGI application named ``name`` in the PasteDeploy config file ``config_file``. - + If the ``name`` is None, this will attempt to parse the name from + the ``config_file`` string expecting the format ``ini_file#name``. + If no name is found, the name will default to "main". + diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 631412f42..be673c370 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -258,8 +258,9 @@ develop``, you can use an interactive Python shell to examine your :app:`Pyramid` project's :term:`resource` and :term:`view` objects from a Python prompt. To do so, use your virtualenv's ``paster pshell`` command. -The first argument to ``pshell`` is the path to your application's ``.ini`` -file. The second is the ``app`` section name inside the ``.ini`` file which +The argument to ``pshell`` follows the format ``config_file#section_name`` +where ``config_file`` is the path to your application's ``.ini`` file and +``section_name`` is the ``app`` section name inside the ``.ini`` file which points to *your application* as opposed to any other section within the ``.ini`` file. For example, if your application ``.ini`` file might have a ``[app:MyProject]`` section that looks like so: @@ -280,16 +281,21 @@ name ``MyProject`` as a section name: .. code-block:: text - [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini MyProject + [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini#MyProject Python 2.4.5 (#1, Aug 29 2008, 12:27:37) [GCC 4.0.1 (Apple Inc. build 5465)] on darwin - Type "help" for more information. "root" is the Pyramid app root object, - "registry" is the Pyramid registry object. + + Default Variables: + app The WSGI Application + root The root of the default resource tree. + registry The Pyramid registry object. + settings The Pyramid settings object. + >>> root >>> registry - >>> registry.settings['debug_notfound'] + >>> settings['debug_notfound'] False >>> from myproject.views import my_view >>> from pyramid.request import Request @@ -297,31 +303,16 @@ name ``MyProject`` as a section name: >>> my_view(r) {'project': 'myproject'} -Two names are made available to the pshell user as globals: ``root`` and -``registry``. ``root`` is the the object returned by the default :term:`root -factory` in your application. ``registry`` is the :term:`application -registry` object associated with your project's application (often accessed -within view code as ``request.registry``). - -If you have `IPython `_ installed in -the interpreter you use to invoke the ``paster`` command, the ``pshell`` -command will use an IPython interactive shell instead of a standard Python -interpreter shell. If you don't want this to happen, even if you have -IPython installed, you can pass the ``--disable-ipython`` flag to the -``pshell`` command to use a standard Python interpreter shell -unconditionally. - -.. code-block:: text - - [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ - development.ini MyProject +The WSGI application that is loaded will be available in the shell as the +``app`` global. Also, if the application that is loaded is the +:app:`Pyramid` app with no surrounding middleware, the ``root`` object +returned by the default :term:`root factory`, ``registry``, and ``settings`` +will be available. -You should always use a section name argument that refers to the actual -``app`` section within the Paste configuration file that points at your -:app:`Pyramid` application *without any middleware wrapping*. In particular, -a section name is inappropriate as the second argument to ``pshell`` if the -configuration section it names is a ``pipeline`` rather than an ``app``. For -example, if you have the following ``.ini`` file content: +The interactive shell will not be able to load some of the globals like +``root``, ``registry`` and ``settings`` if the section name specified when +loading ``pshell`` is not referencing your :app:`Pyramid` application directly. +For example, if you have the following ``.ini`` file content: .. code-block:: ini :linenos: @@ -341,12 +332,51 @@ example, if you have the following ``.ini`` file content: 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. +development.ini#MyProject``). Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). +Extending the Shell +~~~~~~~~~~~~~~~~~~~ + +It is sometimes convenient when using the interactive shell often to have +some variables significant to your application already loaded as globals +when you start the ``pshell``. To facilitate this, ``pshell`` will look +for a special ``[pshell]`` section in your INI file and expose the subsequent +key/value pairs to the shell. + +For example, you want to expose your model to the shell, along with the +database session so that you can mutate the model on an actual database. +Here, we'll assume your model is stored in the ``myapp.models`` package. + +.. code-block:: ini + :linenos: + + [pshell] + m = myapp.models + session = myapp.models.DBSession + t = transaction + +When this INI file is loaded, the extra variables ``m``, ``session`` and +``t`` will be available for use immediately. This happens regardless of +whether the ``registry`` and other special variables are loaded. + +IPython +~~~~~~~ + +If you have `IPython `_ installed in +the interpreter you use to invoke the ``paster`` command, the ``pshell`` +command will use an IPython interactive shell instead of a standard Python +interpreter shell. If you don't want this to happen, even if you have +IPython installed, you can pass the ``--disable-ipython`` flag to the +``pshell`` command to use a standard Python interpreter shell +unconditionally. + +.. code-block:: text + + [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ + development.ini#MyProject + .. index:: single: running an application single: paster serve -- cgit v1.2.3 From 9e7162eb7f584e8afdbc2f04846d0d7e1fcf676c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 7 Jul 2011 02:02:48 -0500 Subject: Updated proutes and pviews docs. --- docs/narr/urldispatch.rst | 9 +++++---- docs/narr/viewconfig.rst | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index f94ed3ba8..51a840b8d 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1084,16 +1084,17 @@ Displaying All Application Routes You can use the ``paster proutes`` command in a terminal window to print a summary of routes related to your application. Much like the ``paster pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` -command accepts two arguments. The first argument to ``proutes`` is the path -to your application's ``.ini`` file. The second is the ``app`` section name -inside the ``.ini`` file which points to your application. +command accepts one argument with the format ``config_file#section_name``. +The ``config_file`` is the path to your application's ``.ini`` file, +and ``section_name`` is the ``app`` section name inside the ``.ini`` file +which points to your application. For example: .. code-block:: text :linenos: - [chrism@thinko MyProject]$ ../bin/paster proutes development.ini MyProject + [chrism@thinko MyProject]$ ../bin/paster proutes development.ini#MyProject Name Pattern View ---- ------- ---- home / diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index ec42446ff..67ac39259 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -795,10 +795,11 @@ For a big application with several views, it can be hard to keep the view configuration details in your head, even if you defined all the views yourself. You can use the ``paster pviews`` command in a terminal window to print a summary of matching routes and views for a given URL in your -application. The ``paster pviews`` command accepts three arguments. The -first argument to ``pviews`` is the path to your application's ``.ini`` file. -The second is the ``app`` section name inside the ``.ini`` file which points -to your application. The third is the URL to test for matching views. +application. The ``paster pviews`` command accepts two arguments. The +first argument to ``pviews`` is the path to your application's ``.ini`` file +and section name inside the ``.ini`` file which points to your application. +This should be of the format ``config_file#section_name``. The second argument +is the URL to test for matching views. Here is an example for a simple view configuration using :term:`traversal`: @@ -829,7 +830,7 @@ A more complex configuration might generate something like this: .. code-block:: text :linenos: - $ ../bin/paster pviews development.ini shootout /about + $ ../bin/paster pviews development.ini#shootout /about URL = /about -- cgit v1.2.3 From deb32f2a8aa996187645bad897051ebe27741d52 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 7 Jul 2011 20:38:20 -0400 Subject: more tests --- pyramid/config.py | 7 +++---- pyramid/tests/test_config.py | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 184c0c6e8..ddf87c043 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2942,10 +2942,9 @@ class ViewDeriver(object): result = view(context, request) response = registry.queryAdapterOrSelf(result, IResponse) if response is None: - if response is None: - raise ValueError( - 'Could not convert view return value "%s" into a ' - 'response object' % (result,)) + raise ValueError( + 'Could not convert view return value "%s" into a ' + 'response object' % (result,)) return response return wrapper diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 0b178d22e..255dd4b6e 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -3981,7 +3981,8 @@ class TestViewDeriver(unittest.TestCase): def outer_view(context, request): self.assertEqual(request.wrapped_response, inner_response) self.assertEqual(request.wrapped_body, inner_response.body) - self.assertEqual(request.wrapped_view, inner_view) + self.assertEqual(request.wrapped_view.__original_view__, + inner_view) return Response('outer ' + request.wrapped_body) self.config.registry.registerAdapter( outer_view, (IViewClassifier, None, None), IView, 'owrap') -- cgit v1.2.3 From 578c4a1d19af3230c12a80c5d94e2b4f2bbbc4e1 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 7 Jul 2011 20:44:51 -0400 Subject: fwd compat for webob 1.1 --- pyramid/tests/test_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 0c1c78721..e65d484ed 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -238,14 +238,14 @@ class TestRequest(unittest.TestCase): self.assertEqual(request.json, None) def test_json_correct_mimetype(self): - request = self._makeOne({}) + request = self._makeOne({'REQUEST_METHOD':'POST'}) request.content_type = 'application/json' request.body = '{"a":1}' self.assertEqual(request.json, {'a':1}) def test_json_alternate_charset(self): from pyramid.compat import json - request = self._makeOne({}) + request = self._makeOne({'REQUEST_METHOD':'POST'}) request.content_type = 'application/json' request.charset = 'latin-1' la = unicode('La Pe\xc3\xb1a', 'utf-8') -- cgit v1.2.3 From d3255c4c452a1bdee1cb3b91bd3f1c7329cdda00 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 8 Jul 2011 02:30:19 -0400 Subject: point at groundhog github repo (why?) --- docs/designdefense.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index ce3c507c5..b285524c6 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1125,10 +1125,11 @@ Self-described "microframeworks" exist: `Bottle `_ and `_ doesn't describe itself as a microframework, but its intended userbase is much the same. Many others exist. We've actually even (only as a teaching tool, not as any sort of official project) -`created one using BFG `_ (the -precursor to Pyramid). Microframeworks are small frameworks with one common -feature: each allows its users to create a fully functional application that -lives in a single Python file. +`created one using Pyramid `_ (the +videos use BFG, a precursor to Pyramid, but the resulting code is `available +for Pyramid too `_). Microframeworks are +small frameworks with one common feature: each allows its users to create a +fully functional application that lives in a single Python file. Some developers and microframework authors point out that Pyramid's "hello world" single-file program is longer (by about five lines) than the -- cgit v1.2.3 From 6784239e26a532b41b73a506b9b4b5a72810cbce Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 01:46:38 -0400 Subject: rename wrapper for better results in profile output --- pyramid/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index ddf87c043..918331405 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2938,7 +2938,7 @@ class ViewDeriver(object): @wraps_view def response_resolved_view(self, view): registry = self.registry - def wrapper(context, request): + def viewresult_to_response(context, request): result = view(context, request) response = registry.queryAdapterOrSelf(result, IResponse) if response is None: @@ -2946,7 +2946,7 @@ class ViewDeriver(object): 'Could not convert view return value "%s" into a ' 'response object' % (result,)) return response - return wrapper + return viewresult_to_response @wraps_view def mapped_view(self, view): -- cgit v1.2.3 From 58bc254ebc222a088f2becef84037693d9aa5c3f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 08:49:10 -0400 Subject: massive test rejiggering to account for the addition of response_resolved_view --- pyramid/config.py | 71 +++++++-------- pyramid/tests/test_config.py | 201 ++++++++++++++++++++++++++----------------- 2 files changed, 154 insertions(+), 118 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 918331405..41af9e832 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2866,54 +2866,42 @@ class MultiView(object): continue raise PredicateMismatch(self.name) -def wraps_view(wrapped): +def wraps_view(wrapper): def inner(self, view): - wrapped_view = wrapped(self, view) - return preserve_view_attrs(view, wrapped_view) + wrapper_view = wrapper(self, view) + return preserve_view_attrs(view, wrapper_view) return inner -def preserve_view_attrs(view, wrapped_view): - if wrapped_view is view: +def preserve_view_attrs(view, wrapper): + if wrapper is view: return view + original_view = getattr(view, '__original_view__', None) + if original_view is None: original_view = view - wrapped_view.__original_view__ = original_view - wrapped_view.__module__ = view.__module__ - wrapped_view.__doc__ = view.__doc__ - try: - wrapped_view.__name__ = view.__name__ - except AttributeError: - wrapped_view.__name__ = repr(view) - try: - wrapped_view.__permitted__ = view.__permitted__ - except AttributeError: - pass - try: - wrapped_view.__call_permissive__ = view.__call_permissive__ - except AttributeError: - pass - try: - wrapped_view.__permission__ = view.__permission__ - except AttributeError: - pass - try: - wrapped_view.__predicated__ = view.__predicated__ - except AttributeError: - pass - try: - wrapped_view.__predicates__ = view.__predicates__ - except AttributeError: - pass - try: - wrapped_view.__accept__ = view.__accept__ - except AttributeError: - pass + + wrapper.__wraps__ = view + wrapper.__original_view__ = original_view + wrapper.__module__ = view.__module__ + wrapper.__doc__ = view.__doc__ + try: - wrapped_view.__order__ = view.__order__ + wrapper.__name__ = view.__name__ except AttributeError: - pass - return wrapped_view + wrapper.__name__ = repr(view) + + # attrs that may not exist on "view", but, if so, must be attached to + # "wrapped view" + for attr in ('__permitted__', '__call_permissive__', '__permission__', + '__predicated__', '__predicates__', '__accept__', + '__order__'): + try: + setattr(wrapper, attr, getattr(view, attr)) + except AttributeError: + pass + + return wrapper class ViewDeriver(object): def __init__(self, **kw): @@ -2938,6 +2926,10 @@ class ViewDeriver(object): @wraps_view def response_resolved_view(self, view): registry = self.registry + if hasattr(registry, '_dont_resolve_responses'): + # for Pyramid unit tests only + return view + def viewresult_to_response(context, request): result = view(context, request) response = registry.queryAdapterOrSelf(result, IResponse) @@ -2946,6 +2938,7 @@ class ViewDeriver(object): 'Could not convert view return value "%s" into a ' 'response object' % (result,)) return response + return viewresult_to_response @wraps_view diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 255dd4b6e..4827cdd70 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -10,7 +10,9 @@ except: class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator - return Configurator(*arg, **kw) + config = Configurator(*arg, **kw) + config.registry._dont_resolve_responses = True + return config def _registerRenderer(self, config, name='.txt'): from pyramid.interfaces import IRendererFactory @@ -211,7 +213,7 @@ class ConfiguratorTests(unittest.TestCase): view = self._getViewCallable(config, ctx_iface=IExceptionResponse, request_iface=IRequest) - self.assertTrue(view is default_exceptionresponse_view) + self.assertTrue(view.__wraps__ is default_exceptionresponse_view) def test_ctor_exceptionresponse_view_None(self): from pyramid.interfaces import IExceptionResponse @@ -230,7 +232,7 @@ class ConfiguratorTests(unittest.TestCase): view = self._getViewCallable(config, ctx_iface=IExceptionResponse, request_iface=IRequest) - self.assertTrue(view is exceptionresponse_view) + self.assertTrue(view.__wraps__ is exceptionresponse_view) def test_with_package_module(self): from pyramid.tests import test_configuration @@ -3190,8 +3192,10 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator - return Configurator(*arg, **kw) - + config = Configurator(*arg, **kw) + config.registry._dont_resolve_responses = True + return config + def _getRouteRequestIface(self, config, name): from pyramid.interfaces import IRouteRequest iface = config.registry.getUtility(IRouteRequest, name) @@ -3498,29 +3502,31 @@ class TestViewDeriver(unittest.TestCase): self.config.registry.registerUtility(policy, IAuthorizationPolicy) def test_requestonly_function(self): + response = DummyResponse() def view(request): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) - self.assertEqual(result(None, None), 'OK') + self.assertEqual(result(None, None), response) def test_requestonly_function_with_renderer(self): + response = DummyResponse() class moo(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, 'OK') self.assertEqual(view_inst, view) self.assertEqual(ctx, context) - return 'moo' + return response def view(request): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) - self.assertFalse(result is view) + self.assertFalse(result.__wraps__ is view) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), 'moo') + self.assertEqual(result(context, request), response) def test_requestonly_function_with_renderer_request_override(self): def moo(info): @@ -3542,46 +3548,50 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result(context, request).body, 'moo') def test_requestonly_function_with_renderer_request_has_view(self): + response = DummyResponse() class moo(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, 'OK') self.assertEqual(view_inst, 'view') self.assertEqual(ctx, context) - return 'moo' + return response def view(request): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) - self.assertFalse(result is view) + self.assertFalse(result.__wraps__ is view) request = self._makeRequest() request.__view__ = 'view' context = testing.DummyResource() - self.assertEqual(result(context, request), 'moo') + r = result(context, request) + self.assertEqual(r, response) self.assertFalse(hasattr(request, '__view__')) def test_class_without_attr(self): + response = DummyResponse() class View(object): def __init__(self, request): pass def __call__(self): - return 'OK' + return response deriver = self._makeOne() result = deriver(View) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, View) def test_class_with_attr(self): + response = DummyResponse() class View(object): def __init__(self, request): pass def another(self): - return 'OK' + return response deriver = self._makeOne(attr='another') result = deriver(View) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, View) def test_as_function_context_and_request(self): @@ -3589,13 +3599,14 @@ class TestViewDeriver(unittest.TestCase): return 'OK' deriver = self._makeOne() result = deriver(view) - self.assertTrue(result is view) + self.assertTrue(result.__wraps__ is view) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(view(None, None), 'OK') def test_as_function_requestonly(self): + response = DummyResponse() def view(request): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) @@ -3603,14 +3614,15 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') + self.assertEqual(result(None, None), response) def test_as_newstyle_class_context_and_request(self): + response = DummyResponse() class view(object): def __init__(self, context, request): pass def __call__(self): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) @@ -3619,15 +3631,16 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_newstyle_class_requestonly(self): + response = DummyResponse() class view(object): def __init__(self, context, request): pass def __call__(self): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) @@ -3636,15 +3649,16 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_oldstyle_class_context_and_request(self): + response = DummyResponse() class view: def __init__(self, context, request): pass def __call__(self): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) @@ -3653,15 +3667,16 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_oldstyle_class_requestonly(self): + response = DummyResponse() class view: def __init__(self, context, request): pass def __call__(self): - return 'OK' + return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) @@ -3670,24 +3685,26 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_instance_context_and_request(self): + response = DummyResponse() class View: def __call__(self, context, request): - return 'OK' + return response view = View() deriver = self._makeOne() result = deriver(view) - self.assertTrue(result is view) + self.assertTrue(result.__wraps__ is view) self.assertFalse(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') + self.assertEqual(result(None, None), response) def test_as_instance_requestonly(self): + response = DummyResponse() class View: def __call__(self, request): - return 'OK' + return response view = View() deriver = self._makeOne() result = deriver(view) @@ -3696,10 +3713,11 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__doc__, result.__doc__) self.assertTrue('instance' in result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') + self.assertEqual(result(None, None), response) def test_with_debug_authorization_no_authpol(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() @@ -3712,7 +3730,7 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " @@ -3720,7 +3738,8 @@ class TestViewDeriver(unittest.TestCase): "(no authorization policy in use)") def test_with_debug_authorization_authn_policy_no_authz_policy(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict(debug_authorization=True) from pyramid.interfaces import IAuthenticationPolicy policy = DummySecurityPolicy(False) @@ -3735,7 +3754,7 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " @@ -3743,7 +3762,8 @@ class TestViewDeriver(unittest.TestCase): "(no authorization policy in use)") def test_with_debug_authorization_authz_policy_no_authn_policy(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict(debug_authorization=True) from pyramid.interfaces import IAuthorizationPolicy policy = DummySecurityPolicy(False) @@ -3758,7 +3778,7 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " @@ -3766,7 +3786,8 @@ class TestViewDeriver(unittest.TestCase): "(no authorization policy in use)") def test_with_debug_authorization_no_permission(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) self._registerSecurityPolicy(True) @@ -3780,7 +3801,7 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " @@ -3788,7 +3809,8 @@ class TestViewDeriver(unittest.TestCase): "no permission registered)") def test_debug_auth_permission_authpol_permitted(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() @@ -3798,18 +3820,19 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result.__call_permissive__, view) + self.assertEqual(result.__call_permissive__.__wraps__, view) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): True") def test_debug_auth_permission_authpol_permitted_no_request(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() @@ -3819,8 +3842,8 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result.__call_permissive__, view) - self.assertEqual(result(None, None), 'OK') + self.assertEqual(result.__call_permissive__.__wraps__, view) + self.assertEqual(result(None, None), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url None (view name " @@ -3828,7 +3851,8 @@ class TestViewDeriver(unittest.TestCase): def test_debug_auth_permission_authpol_denied(self): from pyramid.httpexceptions import HTTPForbidden - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() @@ -3838,7 +3862,7 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result.__call_permissive__, view) + self.assertEqual(result.__call_permissive__.__wraps__, view) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' @@ -3866,7 +3890,8 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(permitted, False) def test_debug_auth_permission_authpol_overridden(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() @@ -3880,14 +3905,15 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): False") def test_secured_view_authn_policy_no_authz_policy(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = {} from pyramid.interfaces import IAuthenticationPolicy policy = DummySecurityPolicy(False) @@ -3901,10 +3927,11 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) def test_secured_view_authz_policy_no_authn_policy(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response self.config.registry.settings = {} from pyramid.interfaces import IAuthorizationPolicy policy = DummySecurityPolicy(False) @@ -3918,10 +3945,11 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' - self.assertEqual(result(None, request), 'OK') + self.assertEqual(result(None, request), response) def test_with_predicates_all(self): - view = lambda *arg: 'OK' + response = DummyResponse() + view = lambda *arg: response predicates = [] def predicate1(context, request): predicates.append(True) @@ -3934,7 +3962,7 @@ class TestViewDeriver(unittest.TestCase): request = self._makeRequest() request.method = 'POST' next = result(None, None) - self.assertEqual(next, 'OK') + self.assertEqual(next, response) self.assertEqual(predicates, [True, True]) def test_with_predicates_checker(self): @@ -4007,13 +4035,14 @@ class TestViewDeriver(unittest.TestCase): self.assertRaises(ValueError, wrapped, None, request) def test_as_newstyle_class_context_and_request_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) - return resp + return response class View(object): def __init__(self, context, request): pass @@ -4027,16 +4056,17 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_as_newstyle_class_requestonly_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) - return resp + return response class View(object): def __init__(self, request): pass @@ -4050,16 +4080,17 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_as_oldstyle_cls_context_request_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) - return resp + return response class View: def __init__(self, context, request): pass @@ -4073,16 +4104,17 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_as_oldstyle_cls_requestonly_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) - return resp + return response class View: def __init__(self, request): pass @@ -4096,16 +4128,17 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_as_instance_context_and_request_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst, view) self.assertEqual(ctx, context) - return resp + return response class View: def index(self, context, request): return {'a':'1'} @@ -4117,16 +4150,17 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_as_instance_requestonly_attr_and_renderer(self): + response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst, view) self.assertEqual(ctx, context) - return resp + return response class View: def index(self, request): return {'a':'1'} @@ -4138,58 +4172,63 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() context = testing.DummyResource() - self.assertEqual(result(context, request), {'a':'1'}) + self.assertEqual(result(context, request), response) def test_with_view_mapper_config_specified(self): + response = DummyResponse() class mapper(object): def __init__(self, **kw): self.kw = kw def __call__(self, view): def wrapped(context, request): - return 'OK' + return response return wrapped def view(context, request): return 'NOTOK' deriver = self._makeOne(mapper=mapper) result = deriver(view) - self.assertFalse(result is view) - self.assertEqual(result(None, None), 'OK') + self.assertFalse(result.__wraps__ is view) + self.assertEqual(result(None, None), response) def test_with_view_mapper_view_specified(self): + from pyramid.response import Response + response = Response() def mapper(**kw): def inner(view): def superinner(context, request): self.assertEqual(request, None) - return 'OK' + return response return superinner return inner def view(context, request): return 'NOTOK' view.__view_mapper__ = mapper deriver = self._makeOne() result = deriver(view) - self.assertFalse(result is view) - self.assertEqual(result(None, None), 'OK') + self.assertFalse(result.__wraps__ is view) + self.assertEqual(result(None, None), response) def test_with_view_mapper_default_mapper_specified(self): + from pyramid.response import Response + response = Response() def mapper(**kw): def inner(view): def superinner(context, request): self.assertEqual(request, None) - return 'OK' + return response return superinner return inner self.config.set_view_mapper(mapper) def view(context, request): return 'NOTOK' deriver = self._makeOne() result = deriver(view) - self.assertFalse(result is view) - self.assertEqual(result(None, None), 'OK') + self.assertFalse(result.__wraps__ is view) + self.assertEqual(result(None, None), response) def test_attr_wrapped_view_branching_default_phash(self): from pyramid.config import DEFAULT_PHASH def view(context, request): pass deriver = self._makeOne(phash=DEFAULT_PHASH) result = deriver(view) - self.assertEqual(result, view) + self.assertEqual(result.__wraps__, view) def test_attr_wrapped_view_branching_nondefault_phash(self): def view(context, request): pass @@ -5405,4 +5444,8 @@ def parse_httpdate(s): def assert_similar_datetime(one, two): for attr in ('year', 'month', 'day', 'hour', 'minute'): assert(getattr(one, attr) == getattr(two, attr)) + +from pyramid.interfaces import IResponse +class DummyResponse(object): + implements(IResponse) -- cgit v1.2.3 From b44aab103120749bd42e531974f28e67c16a1be0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 08:52:23 -0400 Subject: add a note about response wrapping bug fix --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 041ff0bb7..feabf0e26 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -65,6 +65,14 @@ Features to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. +Bug Fixes +--------- + +- Framework wrappers of the original view (such as http_cached and so on) + relied on being able to trust that the response they were receiving was an + IResponse. It wasn't always, because the response was resolved by the + router instead of early in the view wrapping process. This has been fixed. + 1.1a4 (2011-07-01) ================== -- cgit v1.2.3 From 35259d7b1f029391a839c96f7750d6b3433ad2c9 Mon Sep 17 00:00:00 2001 From: ejo Date: Sat, 9 Jul 2011 13:29:38 -0700 Subject: Old sentence was grammatically incorrect, literally meant that the URL or button in question did not know it was redirecting the user. It is the user who does not know, so "unwittingly" is replaced with "secretly"; "surreptitiously" would be another accurate alternative. An alternative sentence construction that maintains the word "unwittingly" would be, e.g., "...might click on a URL or button on another website and be unwittingly redirected to your application to perform some command that requires elevated privileges." --- docs/narr/sessions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 97e3ebc55..365ee395b 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -288,7 +288,7 @@ Preventing Cross-Site Request Forgery Attacks `Cross-site request forgery `_ attacks are a phenomenon whereby a user with an identity on your website might click on a -URL or button on another website which unwittingly redirects the user to your +URL or button on another website which secretly redirects the user to your application to perform some command that requires elevated privileges. You can avoid most of these attacks by making sure that the correct *CSRF -- cgit v1.2.3 From 6a0602b3ce4d2a6de9dca25d8e0d390796a79267 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 21:12:06 -0400 Subject: request.json -> request.json_body; add some docs for json_body --- CHANGES.txt | 13 ++++++---- docs/api/request.rst | 10 ++++---- docs/glossary.rst | 3 +++ docs/narr/webob.rst | 55 +++++++++++++++++++++++++++++++++++++++++++ docs/whatsnew-1.1.rst | 7 +++--- pyramid/request.py | 5 ++-- pyramid/tests/test_request.py | 21 ++++++++++------- 7 files changed, 89 insertions(+), 25 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index feabf0e26..ff4036036 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,10 +9,9 @@ Features object created by Pyramid. (See the Venusian documentation for more information about ``Scanner``). -- New request attribute: ``json``. If the request's ``content_type`` is - ``application/json``, this attribute will contain the JSON-decoded - variant of the request body. If the request's ``content_type`` is not - ``application/json``, this attribute will be ``None``. +- New request property: ``json_body``. This property will return the + JSON-decoded variant of the request body. If the request body is not + well-formed JSON, this property will raise an exception. - A new value ``http_cache`` can be used as a view configuration parameter. @@ -73,6 +72,12 @@ Bug Fixes IResponse. It wasn't always, because the response was resolved by the router instead of early in the view wrapping process. This has been fixed. +Documentation +------------- + +- Added a section in the "Webob" chapter named "Dealing With A JSON-Encoded + Request Body" (usage of ``request.json_body``). + 1.1a4 (2011-07-01) ================== diff --git a/docs/api/request.rst b/docs/api/request.rst index 5dfb2ae9a..404825d1b 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -180,12 +180,12 @@ object (exposed to view code as ``request.response``) to influence rendered response behavior. - .. attribute:: json + .. attribute:: json_body - If the request's ``content_type`` is ``application/json``, this - attribute will contain the JSON-decoded variant of the request body. - If the request's ``content_type`` is not ``application/json``, this - attribute will be ``None``. + This property will return the JSON-decoded variant of the request + body. If the request body is not well-formed JSON, or there is no + body associated with this request, this property will raise an + exception. See also :ref:`request_json_body`. .. note:: diff --git a/docs/glossary.rst b/docs/glossary.rst index e45317dae..c8943acae 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -506,6 +506,9 @@ Glossary `JavaScript Object Notation `_ is a data serialization format. + jQuery + A popular `Javascript library `_. + renderer A serializer that can be referred to via :term:`view configuration` which converts a non-:term:`Response` return diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 0ff8e1de7..373ae5896 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -78,6 +78,10 @@ object: ``PUT``. You can also get ``req.body_file`` for a file-like object. +``req.json_body`` + The JSON-decoded contents of the body of the request. See + :ref:`request_json_body`. + ``req.cookies``: A simple dictionary of all the cookies. @@ -239,6 +243,57 @@ tuples; all the keys are ordered, and all the values are ordered. API documentation for a multidict exists as :class:`pyramid.interfaces.IMultiDict`. +.. _request_json_body: + +Dealing With A JSON-Encoded Request Body +++++++++++++++++++++++++++++++++++++++++ + +.. note:: this feature is new as of Pyramid 1.1. + +:attr:`pyramid.request.Request.json_body` is a property that returns a +:term:`JSON` -decoded representation of the request body. If the request +does not have a body, or the body is not a properly JSON-encoded value, an +exception will be raised when this attribute is accessed. + +This attribute is useful when you invoke a Pyramid view callable via +e.g. jQuery's ``$.post`` or ``$.ajax`` functions, which have the potential to +send a JSON-encoded body or parameters. + +Using ``request.json_body`` is equivalent to: + +.. code-block:: python + + from json import loads + loads(request.body, encoding=request.charset) + +Here's how to construct an AJAX request in Javascript using :term:`jQuery` +that allows you to use the ``request.json_body`` attribute when the request +is sent to a Pyramid application: + +.. code-block:: javascript + + jQuery.ajax({type:'POST', + url: 'http://localhost:6543/', // the pyramid server + data: JSON.stringify({'a':1}), + contentType: 'application/json; charset=utf-8', + dataType: 'json'}); + +When such a request reaches a view in your application, the +``request.json_body`` attribute will be available in the view callable body. + +.. code-block:: javascript + + @view_config(renderer='json') + def aview(request): + print request.json_body + return {'result':'OK'} + +For the above view, printed to the console will be: + +.. code-block:: python + + {u'a': 1} + More Details ++++++++++++ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index a6bb8e99d..a9df38a45 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -99,10 +99,9 @@ Minor Feature Additions the Venusian ``Scanner`` object created by Pyramid. (See the :term:`Venusian` documentation for more information about ``Scanner``). -- New request attribute: ``json``. If the request's ``content_type`` is - ``application/json``, this attribute will contain the JSON-decoded - variant of the request body. If the request's ``content_type`` is not - ``application/json``, this attribute will be ``None``. +- New request property: ``json_body``. This property will return the + JSON-decoded variant of the request body. If the request body is not + well-formed JSON, this property will raise an exception. - A new value ``http_cache`` can be used as a :term:`view configuration` parameter. diff --git a/pyramid/request.py b/pyramid/request.py index a3848461f..1bf044b69 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -491,9 +491,8 @@ class Request(BaseRequest, DeprecatedRequestMethods): return adapted is ob @property - def json(self): - if self.content_type == 'application/json': - return json.loads(self.body, encoding=self.charset) + def json_body(self): + return json.loads(self.body, encoding=self.charset) def route_request_iface(name, bases=()): diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index e65d484ed..74bc25359 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -233,25 +233,28 @@ class TestRequest(unittest.TestCase): request.registry.registerAdapter(adapter, (Foo,), IResponse) self.assertEqual(request.is_response(foo), True) - def test_json_incorrect_mimetype(self): - request = self._makeOne({}) - self.assertEqual(request.json, None) + def test_json_body_invalid_json(self): + request = self._makeOne({'REQUEST_METHOD':'POST'}) + request.body = '{' + self.assertRaises(ValueError, getattr, request, 'json_body') - def test_json_correct_mimetype(self): + def test_json_body_valid_json(self): request = self._makeOne({'REQUEST_METHOD':'POST'}) - request.content_type = 'application/json' request.body = '{"a":1}' - self.assertEqual(request.json, {'a':1}) + self.assertEqual(request.json_body, {'a':1}) - def test_json_alternate_charset(self): + def test_json_body_alternate_charset(self): from pyramid.compat import json request = self._makeOne({'REQUEST_METHOD':'POST'}) - request.content_type = 'application/json' request.charset = 'latin-1' la = unicode('La Pe\xc3\xb1a', 'utf-8') body = json.dumps({'a':la}, encoding='latin-1') request.body = body - self.assertEqual(request.json, {'a':la}) + self.assertEqual(request.json_body, {'a':la}) + + def test_json_body_GET_request(self): + request = self._makeOne({'REQUEST_METHOD':'GET'}) + self.assertRaises(ValueError, getattr, request, 'json_body') class TestRequestDeprecatedMethods(unittest.TestCase): def setUp(self): -- cgit v1.2.3 From e3349693c533c17fb9b6a770a8360b64ec337c68 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 21:24:14 -0400 Subject: make less confusing --- docs/narr/webob.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 373ae5896..beb319084 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -256,8 +256,8 @@ does not have a body, or the body is not a properly JSON-encoded value, an exception will be raised when this attribute is accessed. This attribute is useful when you invoke a Pyramid view callable via -e.g. jQuery's ``$.post`` or ``$.ajax`` functions, which have the potential to -send a JSON-encoded body or parameters. +e.g. jQuery's ``$.ajax`` function, which has the potential to send a request +with a JSON-encoded body. Using ``request.json_body`` is equivalent to: @@ -275,18 +275,17 @@ is sent to a Pyramid application: jQuery.ajax({type:'POST', url: 'http://localhost:6543/', // the pyramid server data: JSON.stringify({'a':1}), - contentType: 'application/json; charset=utf-8', - dataType: 'json'}); + contentType: 'application/json; charset=utf-8'}); When such a request reaches a view in your application, the ``request.json_body`` attribute will be available in the view callable body. .. code-block:: javascript - @view_config(renderer='json') + @view_config(renderer='string') def aview(request): print request.json_body - return {'result':'OK'} + return 'OK' For the above view, printed to the console will be: -- cgit v1.2.3 From 8a77566378f176d7ebb09ba6a573cde29481790f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 21:55:53 -0400 Subject: prevent an extra function call when a renderer is in use --- pyramid/config.py | 65 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index c4f75c39d..aa02c3d69 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2935,27 +2935,8 @@ class ViewDeriver(object): self.owrapped_view( self.decorated_view( self.http_cached_view( - self.response_resolved_view( - self.rendered_view( - self.mapped_view(view)))))))))) - - @wraps_view - def response_resolved_view(self, view): - registry = self.registry - if hasattr(registry, '_dont_resolve_responses'): - # for Pyramid unit tests only - return view - - def viewresult_to_response(context, request): - result = view(context, request) - response = registry.queryAdapterOrSelf(result, IResponse) - if response is None: - raise ValueError( - 'Could not convert view return value "%s" into a ' - 'response object' % (result,)) - return response - - return viewresult_to_response + self.rendered_view( + self.mapped_view(view))))))))) @wraps_view def mapped_view(self, view): @@ -3119,21 +3100,23 @@ class ViewDeriver(object): @wraps_view def rendered_view(self, view): - wrapped_view = view - static_renderer = self.kw.get('renderer') - if static_renderer is None: + # one way or another this wrapper must produce a Response + renderer = self.kw.get('renderer') + if renderer is None: # register a default renderer if you want super-dynamic # rendering. registering a default renderer will also allow # override_renderer to work if a renderer is left unspecified for # a view registration. - return view + return self._response_resolved_view(view) + return self._rendered_view(view, renderer) - def _rendered_view(context, request): - renderer = static_renderer - result = wrapped_view(context, request) - registry = self.kw['registry'] + def _rendered_view(self, view, view_renderer): + def rendered_view(context, request): + renderer = view_renderer + result = view(context, request) + registry = self.registry # this must adapt, it can't do a simple interface check - # (webob responses) + # (avoid trying to render webob responses) response = registry.queryAdapterOrSelf(result, IResponse) if response is None: attrs = getattr(request, '__dict__', {}) @@ -3146,13 +3129,29 @@ class ViewDeriver(object): if '__view__' in attrs: view_inst = attrs.pop('__view__') else: - view_inst = getattr(wrapped_view, '__original_view__', - wrapped_view) + view_inst = getattr(view, '__original_view__', view) response = renderer.render_view(request, result, view_inst, context) return response - return _rendered_view + return rendered_view + + def _response_resolved_view(self, view): + registry = self.registry + if hasattr(registry, '_dont_resolve_responses'): + # for Pyramid unit tests only + return view + + def viewresult_to_response(context, request): + result = view(context, request) + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + raise ValueError( + 'Could not convert view return value "%s" into a ' + 'response object' % (result,)) + return response + + return viewresult_to_response @wraps_view def decorated_view(self, view): -- cgit v1.2.3 From 5aeb91f8a197a601e8edfaf00e1d7000b3912b9b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 23:34:08 -0400 Subject: add release ann template --- RELEASING.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/RELEASING.txt b/RELEASING.txt index 645083acf..bb2dab87f 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -48,4 +48,29 @@ Releasing Pyramid - Announce to Twitter. +Announcement template +---------------------- +Pyramid 1.1.X has been released. + +Here are the changes: + +<> + +A "What's New In Pyramid 1.1" document exists at +http://docs.pylonsproject.org/projects/pyramid/1.1/whatsnew-1.1.html . + +You will be able to see the 1.1 release documentation (across all +alphas and betas, as well as when it eventually gets to final release) +at http://docs.pylonsproject.org/projects/pyramid/1.1/ . + +You can install it via PyPI: + + easy_install Pyramid==1.1a4 + +Enjoy, and please report any issues you find to the issue tracker at +https://github.com/Pylons/pyramid/issues + +Thanks! + +- C -- cgit v1.2.3 From 99d99019e75a7190a0154ec12845e67fc99aafb9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 23:53:30 -0400 Subject: add prevent_auto flag to cache control --- pyramid/config.py | 10 +++++++++- pyramid/tests/test_config.py | 9 +++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index aa02c3d69..53daf3b56 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1140,6 +1140,12 @@ class Configurator(object): tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. + If you wish to prevent a view that uses ``http_cache`` in its + configuration from having its response changed by ``http_cache`` , + set ``response.cache_control.prevent_auto = True`` before returning + the response. This effectively disables any HTTP caching done by + ``http_cache`` for that response. + wrapper The :term:`view name` of a different :term:`view @@ -2990,7 +2996,9 @@ class ViewDeriver(object): def wrapper(context, request): response = view(context, request) - response.cache_expires(seconds, **options) + cache_control = response.cache_control + if not hasattr(cache_control, 'prevent_auto'): + response.cache_expires(seconds, **options) return response return wrapper diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ad5e5a89a..d7e62da0a 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -4318,19 +4318,20 @@ class TestViewDeriver(unittest.TestCase): self.assertFalse('Expires' in headers) self.assertEqual(headers['Cache-Control'], 'public') - def test_http_cached_view_nonresponse_object_returned_downstream(self): + def test_http_cached_view_prevent_auto_set(self): from pyramid.response import Response response = Response() + response.cache_control.prevent_auto = True def inner_view(context, request): return response deriver = self._makeOne(http_cache=3600) result = deriver(inner_view) - self.assertFalse(result is inner_view) - self.assertEqual(inner_view.__module__, result.__module__) - self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() result = result(None, request) self.assertEqual(result, response) # doesn't blow up + headers = dict(result.headerlist) + self.assertFalse('Expires' in headers) + self.assertFalse('Cache-Control' in headers) def test_http_cached_view_bad_tuple(self): from pyramid.exceptions import ConfigurationError -- cgit v1.2.3 From 8fa04b6ba5b1687e504a4b984fdd0a3e9b8279d9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 9 Jul 2011 23:54:19 -0400 Subject: add prevent_auto flag to cache control --- pyramid/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 53daf3b56..607dbad38 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1141,10 +1141,10 @@ class Configurator(object): ``(None, {'public':True})``. If you wish to prevent a view that uses ``http_cache`` in its - configuration from having its response changed by ``http_cache`` , - set ``response.cache_control.prevent_auto = True`` before returning - the response. This effectively disables any HTTP caching done by - ``http_cache`` for that response. + configuration from having its caching response headers changed by + this machinery, set ``response.cache_control.prevent_auto = True`` + before returning the response from the view. This effectively + disables any HTTP caching done by ``http_cache`` for that response. wrapper -- cgit v1.2.3 From 8cad6080cda9a72902333a70c20b58c5ea02226c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 10 Jul 2011 00:03:28 -0400 Subject: reverse position of custom decorator and http cache decorator --- pyramid/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 607dbad38..2e018f66f 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2939,8 +2939,8 @@ class ViewDeriver(object): self.authdebug_view( self.secured_view( self.owrapped_view( - self.decorated_view( - self.http_cached_view( + self.http_cached_view( + self.decorated_view( self.rendered_view( self.mapped_view(view))))))))) -- cgit v1.2.3 From 7565006cf1f3b929d9ea54256214f3a39385936a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 10 Jul 2011 00:30:30 -0400 Subject: add info to changes.txt and whatsnew about pshell changes; removed unused import and unwrap string --- CHANGES.txt | 24 ++++++++++++++++++++++++ docs/narr/project.rst | 2 ++ docs/whatsnew-1.1.rst | 19 +++++++++++++++++++ pyramid/paster.py | 3 +-- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ff4036036..ae8395234 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,21 @@ Next release Features -------- +- It is now possible to invoke ``paster pshell`` even if the paste ini file + section name pointed to in its argument is not actually a Pyramid WSGI + application. The shell will work in a degraded mode, and will warn the + user. See "The Interactive Shell" in the "Creating a Pyramid Project" + narrative documentation section. + +- ``paster pshell`` now offers more built-in global variables by default + (including ``app`` and ``settings``). See "The Interactive Shell" in the + "Creating a Pyramid Project" narrative documentation section. + +- It is now possible to add a ``[pshell]`` section to your application's .ini + configuration file, which influences the global names available to a pshell + session. See "Extending the Shell" in the "Creating a Pyramid Project" + narrative documentation chapter. + - The ``config.scan`` method has grown a ``**kw`` argument. ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object created by Pyramid. (See the Venusian documentation for more @@ -78,6 +93,15 @@ Documentation - Added a section in the "Webob" chapter named "Dealing With A JSON-Encoded Request Body" (usage of ``request.json_body``). +Behavior Changes +---------------- + +- The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands + now take a single argument in the form ``/path/to/config.ini#sectionname`` + rather than the previous 2-argument spelling ``/path/to/config.ini + sectionname``. ``#sectionname`` may be omitted, in which case ``#main`` is + assumed. + 1.1a4 (2011-07-01) ================== diff --git a/docs/narr/project.rst b/docs/narr/project.rst index be673c370..ab7023561 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -336,6 +336,8 @@ development.ini#MyProject``). Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). +.. _extending_pshell: + Extending the Shell ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index a9df38a45..b249b0ba7 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -94,6 +94,19 @@ Default HTTP Exception View Minor Feature Additions ----------------------- +- It is now possible to invoke ``paster pshell`` even if the paste ini file + section name pointed to in its argument is not actually a Pyramid WSGI + application. The shell will work in a degraded mode, and will warn the + user. See "The Interactive Shell" in the "Creating a Pyramid Project" + narrative documentation section. + +- ``paster pshell`` now offers more built-in global variables by default + (including ``app`` and ``settings``). See :ref:`interactive_shell`. + +- It is now possible to add a ``[pshell]`` section to your application's .ini + configuration file, which influences the global names available to a pshell + session. See :ref:`extending_pshell`. + - The :meth:`pyramid.config.Configurator.scan` method has grown a ``**kw`` argument. ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object created by Pyramid. (See the @@ -295,6 +308,12 @@ Backwards Incompatibilities Deprecations and Behavior Differences ------------------------------------- +- The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands + now take a single argument in the form ``/path/to/config.ini#sectionname`` + rather than the previous 2-argument spelling ``/path/to/config.ini + sectionname``. ``#sectionname`` may be omitted, in which case ``#main`` is + assumed. + - The default Mako renderer is now configured to escape all HTML in expression tags. This is intended to help prevent XSS attacks caused by rendering unsanitized input from users. To revert this behavior in user's diff --git a/pyramid/paster.py b/pyramid/paster.py index eabd12a1b..ad807d53f 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,6 +1,5 @@ import ConfigParser import os -import re import sys from code import interact @@ -105,7 +104,7 @@ class PShellCommand(PCommand): from IPython.Shell import IPShell except ImportError: IPShell = None - cprt =('Type "help" for more information.') + cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) app_spec = self.args[0] config_file = app_spec.split('#', 1)[0] -- cgit v1.2.3 From 1cb0d740e6b11d2ad5e490c3a64b834640195879 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 10 Jul 2011 00:40:26 -0400 Subject: slightly more informative error message --- pyramid/paster.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index ad807d53f..a5cd63dfb 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -129,9 +129,12 @@ class PShellCommand(PCommand): else: # warn the user that this isn't actually the Pyramid app warning = """\n -WARNING: You have loaded a generic WSGI application, therefore the -"root" and "registry" are not available. To correct this, run "pshell" -again and specify the INI section containing your Pyramid application.""" +WARNING: You have loaded a generic WSGI application, therefore the "root", +"registry", and "settings" global variables are not available. To correct +this, run "pshell" again and specify the INI section containing your Pyramid +application. For example, if your app is in the '[app:myapp]' config file +section, use 'development.ini#myapp' instead of 'development.ini' or +'development.ini#main'.""" closer = lambda: None # load the pshell section of the ini file -- cgit v1.2.3 From a36fdbd40967117d34a70f156d2865aa2b21991e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 10 Jul 2011 01:13:44 -0400 Subject: prep for 1.1b1 --- 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 ae8395234..2dead04b4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.1b1 (2011-07-10) +================== Features -------- diff --git a/docs/conf.py b/docs/conf.py index f09cd63f5..31d816aff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.1a4' +version = '1.1b1' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index 3b57d6698..54e45e14a 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1a4', + version='1.1b1', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From 4b105d5853988f846a0ff604e811b436b8a68f50 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 10 Jul 2011 02:16:18 -0400 Subject: garden --- TODO.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index ab0e6da41..ee5275952 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,17 +4,14 @@ Pyramid TODOs Must-Have --------- -- Github issues fixes. - - add_route discriminator wrong - tutorial models.initialize_sql doesn't match scaffold (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in tutorial) -- Allow views to override http_cache headers. - -- request.JSON dictionary? +- deprecate request.add_response_callback (or at least review docs, in light + of request.response property). Should-Have ----------- -- cgit v1.2.3 From 546ae051d076de48496b721f13fd74c3f4c12a67 Mon Sep 17 00:00:00 2001 From: ejo Date: Sun, 10 Jul 2011 10:38:01 -0700 Subject: Adding 'I' to example custom AuthenticationPolicy; it's an interface. --- docs/narr/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/security.rst b/docs/narr/security.rst index c7a07b857..322e905f6 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -562,7 +562,7 @@ that implements the following interface: .. code-block:: python :linenos: - class AuthenticationPolicy(object): + class IAuthenticationPolicy(object): """ An object representing a Pyramid authentication policy. """ def authenticated_userid(self, request): -- cgit v1.2.3 From e005c27ae54f12d5f9579451c1c894a534eb7d48 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Jul 2011 22:06:51 -0500 Subject: Modified docs to reference webob's new website. --- docs/glossary.rst | 2 +- docs/narr/views.rst | 2 +- docs/narr/webob.rst | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index c8943acae..0a1c363f4 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -326,7 +326,7 @@ Glossary :term:`ZODB` database. WebOb - `WebOb `_ is a WSGI request/response + `WebOb `_ is a WSGI request/response library created by Ian Bicking. Paste diff --git a/docs/narr/views.rst b/docs/narr/views.rst index cbd8fcfb7..ca5aac508 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -483,7 +483,7 @@ various other clients. In :app:`Pyramid`, form submission handling logic is always part of a :term:`view`. For a general overview of how to handle form submission data using the :term:`WebOb` API, see :ref:`webob_chapter` and `"Query and POST variables" within the WebOb documentation -`_. +`_. :app:`Pyramid` defers to WebOb for its request and response implementations, and handling form submission data is a property of the request implementation. Understanding WebOb's request API is the key to diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index beb319084..0d928e532 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -22,9 +22,9 @@ of :class:`webob.Request`. The :term:`response` returned from a WebOb is a project separate from :app:`Pyramid` with a separate set of authors and a fully separate `set of documentation -`_. Pyramid adds some functionality to the -standard WebOb request, which is documented in the :ref:`request_module` API -documentation. +`_. Pyramid adds some +functionality to the standard WebOb request, which is documented in the +:ref:`request_module` API documentation. WebOb provides objects for HTTP requests and responses. Specifically it does this by wrapping the `WSGI `_ request environment and @@ -35,7 +35,7 @@ requests and forming WSGI responses. WebOb is a nice way to represent "raw" WSGI requests and responses; however, we won't cover that use case in this document, as users of :app:`Pyramid` don't typically need to use the WSGI-related features of WebOb directly. The `reference documentation -`_ shows many examples of +`_ shows many examples of creating requests and using response objects in this manner, however. .. index:: @@ -300,8 +300,8 @@ More detail about the request object API is available in: - The :class:`pyramid.request.Request` API documentation. -- The `WebOb documentation `_. All - methods and attributes of a ``webob.Request`` documented within the +- The `WebOb documentation `_. + All methods and attributes of a ``webob.Request`` documented within the WebOb documentation will work with request objects created by :app:`Pyramid`. @@ -385,7 +385,7 @@ properties. These are parsed, so you can do things like ``response.last_modified = os.path.getmtime(filename)``. The details are available in the `extracted Response documentation -`_. +`_. .. index:: single: response (creating) @@ -444,5 +444,6 @@ More Details More details about the response object API are available in the :mod:`pyramid.response` documentation. More details about exception responses are in the :mod:`pyramid.httpexceptions` API documentation. The -`WebOb documentation `_ is also useful. +`WebOb documentation `_ is also +useful. -- cgit v1.2.3 From 71a5ae65dc74a7d4e07f0c74c911a449576237b5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 03:52:15 -0400 Subject: move alternate calling convention to chapter bottom; fix reference --- docs/narr/views.rst | 150 ++++++++++++++++++++++++++-------------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/docs/narr/views.rst b/docs/narr/views.rst index cbd8fcfb7..6207ae00a 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -134,80 +134,6 @@ related view callables. special kind of view class which provides more automation when your application uses :term:`URL dispatch` solely. -.. index:: - single: view calling convention - -.. _request_and_context_view_definitions: - -Alternate View Callable Argument/Calling Conventions ----------------------------------------------------- - -Usually, view callables are defined to accept only a single argument: -``request``. However, view callables may alternately be defined as classes, -functions, or any callable that accept *two* positional arguments: a -:term:`context` resource as the first argument and a :term:`request` as the -second argument. - -The :term:`context` and :term:`request` arguments passed to a view function -defined in this style can be defined as follows: - -context - - The :term:`resource` object found via tree :term:`traversal` or :term:`URL - dispatch`. - -request - A :app:`Pyramid` Request object representing the current WSGI request. - -The following types work as view callables in this style: - -#. Functions that accept two arguments: ``context``, and ``request``, - e.g.: - - .. code-block:: python - :linenos: - - from pyramid.response import Response - - def view(context, request): - return Response('OK') - -#. Classes that have an ``__init__`` method that accepts ``context, - request`` and a ``__call__`` method which accepts no arguments, e.g.: - - .. code-block:: python - :linenos: - - from pyramid.response import Response - - class view(object): - def __init__(self, context, request): - self.context = context - self.request = request - - def __call__(self): - return Response('OK') - -#. Arbitrary callables that have a ``__call__`` method that accepts - ``context, request``, e.g.: - - .. code-block:: python - :linenos: - - from pyramid.response import Response - - class View(object): - def __call__(self, context, request): - return Response('OK') - view = View() # this is the view callable - -This style of calling convention is most useful for :term:`traversal` based -applications, where the context object is frequently used within the view -callable code itself. - -No matter which view calling convention is used, the view code always has -access to the context via ``request.context``. - .. index:: single: view response single: response @@ -234,7 +160,7 @@ implements the :term:`Response` interface is to return a inherit from :class:`pyramid.response.Response`. For example, an instance of the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response object because it inherits from :class:`~pyramid.response.Response`. For -examples, see :ref:`http_exceptions` and ref:`http_redirect`. +examples, see :ref:`http_exceptions` and :ref:`http_redirect`. You can also return objects from view callables that aren't instances of (or instances of classes which are subclasses of) @@ -591,3 +517,77 @@ using your own response object, you will need to ensure you do this yourself. configuration. The keys are still (byte) strings. +.. index:: + single: view calling convention + +.. _request_and_context_view_definitions: + +Alternate View Callable Argument/Calling Conventions +---------------------------------------------------- + +Usually, view callables are defined to accept only a single argument: +``request``. However, view callables may alternately be defined as classes, +functions, or any callable that accept *two* positional arguments: a +:term:`context` resource as the first argument and a :term:`request` as the +second argument. + +The :term:`context` and :term:`request` arguments passed to a view function +defined in this style can be defined as follows: + +context + + The :term:`resource` object found via tree :term:`traversal` or :term:`URL + dispatch`. + +request + A :app:`Pyramid` Request object representing the current WSGI request. + +The following types work as view callables in this style: + +#. Functions that accept two arguments: ``context``, and ``request``, + e.g.: + + .. code-block:: python + :linenos: + + from pyramid.response import Response + + def view(context, request): + return Response('OK') + +#. Classes that have an ``__init__`` method that accepts ``context, + request`` and a ``__call__`` method which accepts no arguments, e.g.: + + .. code-block:: python + :linenos: + + from pyramid.response import Response + + class view(object): + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + return Response('OK') + +#. Arbitrary callables that have a ``__call__`` method that accepts + ``context, request``, e.g.: + + .. code-block:: python + :linenos: + + from pyramid.response import Response + + class View(object): + def __call__(self, context, request): + return Response('OK') + view = View() # this is the view callable + +This style of calling convention is most useful for :term:`traversal` based +applications, where the context object is frequently used within the view +callable code itself. + +No matter which view calling convention is used, the view code always has +access to the context via ``request.context``. + -- cgit v1.2.3 From f98cb5d2acd274df2507f822bdc269860e39a169 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 03:57:25 -0400 Subject: move mention of controllers to end --- docs/narr/views.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 2a0aae0ed..6acb1d28d 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -129,11 +129,6 @@ statements with different ``attr`` values, each pointing at a different method of the class if you'd like the class to represent a collection of related view callables. -.. note:: A package named :term:`pyramid_handlers` (available from PyPI) - provides an analogue of :term:`Pylons` -style "controllers", which are a - special kind of view class which provides more automation when your - application uses :term:`URL dispatch` solely. - .. index:: single: view response single: response @@ -591,3 +586,11 @@ callable code itself. No matter which view calling convention is used, the view code always has access to the context via ``request.context``. +Pylons-1.0-Style "Controller" Dispatch +-------------------------------------- + +A package named :term:`pyramid_handlers` (available from PyPI) provides an +analogue of :term:`Pylons` -style "controllers", which are a special kind of +view class which provides more automation when your application uses +:term:`URL dispatch` solely. + -- cgit v1.2.3 From 026da8ec4711d3532e5e4d8265bf445d4a329245 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 04:13:38 -0400 Subject: earlier info referenced later info, fix --- docs/narr/viewconfig.rst | 54 +++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 67ac39259..f7509e9db 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -47,7 +47,7 @@ to be invoked. A view configuration statement is made about information present in the :term:`context` resource and the :term:`request`. -View configuration is performed in one of these ways: +View configuration is performed in one of two ways: - by running a :term:`scan` against application source code which has a :class:`pyramid.view.view_config` decorator attached to a Python object as @@ -56,17 +56,6 @@ View configuration is performed in one of these ways: - by using the :meth:`pyramid.config.Configurator.add_view` method as per :ref:`mapping_views_using_imperative_config_section`. -- By specifying a view within a :term:`route configuration`. View - configuration via a route configuration is performed by using the - :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` - argument specifying a view callable. This pattern of view configuration is - deprecated as of :app:`Pyramid` 1.1. - -.. note:: A package named ``pyramid_handlers`` (available from PyPI) provides - an analogue of :term:`Pylons` -style "controllers", which are a special - kind of view class which provides more automation when your application - uses :term:`URL dispatch` solely. - .. _view_configuration_parameters: View Configuration Parameters @@ -408,26 +397,25 @@ configured view. View Configuration Using the ``@view_config`` Decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For better locality of reference, you may use the -:class:`pyramid.view.view_config` decorator to associate your view functions -with URLs instead of using imperative configuration for the same purpose. - .. warning:: Using this feature tends to slows down application startup slightly, as more work is performed at application startup to scan for view - declarations. + declarations. For maximum startup performance, use the view configuration + method described in :ref:`mapping_views_using_imperative_config_section` + instead. Usage of the ``view_config`` decorator is a form of :term:`declarative -configuration` in decorator form. :class:`~pyramid.view.view_config` can be -used to associate :term:`view configuration` information -- as done via the -equivalent imperative code -- with a function that acts as a :app:`Pyramid` -view callable. All arguments to the -:meth:`pyramid.config.Configurator.add_view` method (save for the ``view`` -argument) are available in decorator form and mean precisely the same thing. +configuration`. The :class:`~pyramid.view.view_config` decorator can be used +to associate :term:`view configuration` information with a function that acts +as a :app:`Pyramid` view callable. All arguments to the +:class:`~pyramid.view.view_config` decorator mean precisely the same thing as +they would if they were passed as arguments to the +:meth:`pyramid.config.Configurator.add_view` method save for the ``view`` +argument. -An example of the :class:`~pyramid.view.view_config` decorator might reside in -a :app:`Pyramid` application module ``views.py``: +Here's an example of the :class:`~pyramid.view.view_config` decorator that +lives within a :app:`Pyramid` application module ``views.py``: .. ignore-next-block .. code-block:: python @@ -618,9 +606,10 @@ View Registration Using :meth:`~pyramid.config.Configurator.add_view` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_view` method within -:ref:`configuration_module` is used to configure a view imperatively. The -arguments to this method are very similar to the arguments that you provide -to the ``@view_config`` decorator. For example: +:ref:`configuration_module` is used to configure a view "imperatively" +(without a :class:`~pyramid.view.view_config` decorator. The arguments to +this method are very similar to the arguments that you provide to the +:class:`~pyramid.view.view_config` decorator. For example: .. code-block:: python :linenos: @@ -636,8 +625,13 @@ to the ``@view_config`` decorator. For example: The first argument, ``view``, is required. It must either be a Python object which is the view itself or a :term:`dotted Python name` to such an object. -All other arguments are optional. See -:meth:`pyramid.config.Configurator.add_view` for more information. +In the above example, ``view`` is the ``hello_world`` function. All other +arguments are optional. See :meth:`pyramid.config.Configurator.add_view` for +more information. + +When you use only :meth:`~pyramid.config.Configurator.add_view` to add view +configurations, you don't need to issue a :term:`scan` in order for the view +configuration to take effect. .. index:: single: resource interfaces -- cgit v1.2.3 From e81ad8ca6355d85462508f03496fe7b088986601 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 04:26:26 -0400 Subject: simplify wording --- docs/narr/viewconfig.rst | 91 ++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index f7509e9db..326d97506 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -2,38 +2,23 @@ .. _view_configuration: +.. _view_lookup: + View Configuration ================== .. index:: single: view lookup -:term:`View configuration` controls how :term:`view lookup` operates in -your application. In earlier chapters, you have been exposed to a few -simple view configuration declarations without much explanation. In this -chapter we will explore the subject in detail. - -.. _view_lookup: - -View Lookup and Invocation --------------------------- - :term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding -an invoking a :term:`view callable`. The view lookup subsystem is passed a -:term:`context` and a :term:`request` object. - -:term:`View configuration` information stored within in the -:term:`application registry` is compared against the context and request by -the view lookup subsystem in order to find the "best" view callable for the -set of circumstances implied by the context and request. - -:term:`View predicate` attributes are an important part of view -configuration that enables the :term:`View lookup` subsystem to find and -invoke the appropriate view. Predicate attributes can be thought of -like "narrowers". In general, the greater number of predicate -attributes possessed by a view's configuration, the more specific the -circumstances need to be before the registered view callable will be -invoked. +an invoking a :term:`view callable`. :term:`View configuration` controls how +:term:`view lookup` operates in your application. During any given request, +view configuration information is compared against request data by the view +lookup subsystem in order to find the "best" view callable for that request. + +In earlier chapters, you have been exposed to a few simple view configuration +declarations without much explanation. In this chapter we will explore the +subject in detail. Mapping a Resource or URL Pattern to a View Callable ---------------------------------------------------- @@ -68,12 +53,15 @@ arguments. View predicate arguments used during view configuration are used to narrow the set of circumstances in which :term:`view lookup` will find a particular view callable. -In general, the fewer number of predicates which are supplied to a -particular view configuration, the more likely it is that the associated -view callable will be invoked. The greater the number supplied, the -less likely. A view with five predicates will always be found and -evaluated before a view with two, for example. All predicates must -match for the associated view to be called. +:term:`View predicate` attributes are an important part of view configuration +that enables the :term:`view lookup` subsystem to find and invoke the +appropriate view. The greater number of predicate attributes possessed by a +view's configuration, the more specific the circumstances need to be before +the registered view callable will be invoked. The fewer number of predicates +which are supplied to a particular view configuration, the more likely it is +that the associated view callable will be invoked. A view with five +predicates will always be found and evaluated before a view with two, for +example. All predicates must match for the associated view to be called. This does not mean however, that :app:`Pyramid` "stops looking" when it finds a view registration with predicates that don't match. If one set @@ -88,8 +76,8 @@ the request, :app:`Pyramid` will return an error to the user's browser, representing a "not found" (404) page. See :ref:`changing_the_notfound_view` for more information about changing the default notfound view. -Some view configuration arguments are non-predicate arguments. These tend to -modify the response of the view callable or prevent the view callable from +Other view configuration arguments are non-predicate arguments. These tend +to modify the response of the view callable or prevent the view callable from being invoked due to an authorization policy. The presence of non-predicate arguments in a view configuration does not narrow the circumstances in which the view callable will be invoked. @@ -394,25 +382,20 @@ configured view. .. _mapping_views_using_a_decorator_section: -View Configuration Using the ``@view_config`` Decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding View Configuration Using the ``@view_config`` Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: Using this feature tends to slows down application startup slightly, as more work is performed at application startup to scan for view - declarations. For maximum startup performance, use the view configuration - method described in :ref:`mapping_views_using_imperative_config_section` - instead. - -Usage of the ``view_config`` decorator is a form of :term:`declarative -configuration`. The :class:`~pyramid.view.view_config` decorator can be used -to associate :term:`view configuration` information with a function that acts -as a :app:`Pyramid` view callable. All arguments to the -:class:`~pyramid.view.view_config` decorator mean precisely the same thing as -they would if they were passed as arguments to the -:meth:`pyramid.config.Configurator.add_view` method save for the ``view`` -argument. + configuration declarations. For maximum startup performance, use the view + configuration method described in + :ref:`mapping_views_using_imperative_config_section` instead. + +The :class:`~pyramid.view.view_config` decorator can be used to associate +:term:`view configuration` information with a function, method, or class that +acts as a :app:`Pyramid` view callable. Here's an example of the :class:`~pyramid.view.view_config` decorator that lives within a :app:`Pyramid` application module ``views.py``: @@ -482,6 +465,14 @@ See :ref:`configuration_module` for additional API arguments to the allows you to supply a ``package`` argument to better control exactly *which* code will be scanned. +All arguments to the :class:`~pyramid.view.view_config` decorator mean +precisely the same thing as they would if they were passed as arguments to +the :meth:`pyramid.config.Configurator.add_view` method save for the ``view`` +argument. Usage of the :class:`~pyramid.view.view_config` decorator is a +form of :term:`declarative configuration`, while +:meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative +configuration`. However, they both do the same thing. + ``@view_config`` Placement ++++++++++++++++++++++++++ @@ -602,12 +593,12 @@ against the ``amethod`` method could be spelled equivalently as the below: .. _mapping_views_using_imperative_config_section: -View Registration Using :meth:`~pyramid.config.Configurator.add_view` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding View Configuration Using :meth:`~pyramid.config.Configurator.add_view` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_view` method within :ref:`configuration_module` is used to configure a view "imperatively" -(without a :class:`~pyramid.view.view_config` decorator. The arguments to +(without a :class:`~pyramid.view.view_config` decorator). The arguments to this method are very similar to the arguments that you provide to the :class:`~pyramid.view.view_config` decorator. For example: -- cgit v1.2.3 From 4e1199fdb3219ed7ed3635f8ed96fb0cff18abe1 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 04:35:53 -0400 Subject: simplify --- docs/glossary.rst | 5 +++-- docs/narr/configuration.rst | 44 ++++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 0a1c363f4..3fa2b0261 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -592,8 +592,9 @@ Glossary declaration` required by your application. declarative configuration - The configuration mode in which you use :term:`ZCML` to make a set of - :term:`configuration declaration` statements. See :term:`pyramid_zcml`. + The configuration mode in which you use the combination of + :term:`configuration decorator` decorations and a :term:`scan` to + configure your Pyramid application. Not Found view An :term:`exception view` invoked by :app:`Pyramid` when the diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 6360dc574..4c2870562 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -23,6 +23,10 @@ standardized ways that code gets plugged into a deployment of the framework itself. When you plug code into the :app:`Pyramid` framework, you are "configuring" :app:`Pyramid` to create a particular application. +There are two ways to configure a :app:`Pyramid` application: +:term:`imperative configuration` and :term:`declarative configuration`. Both +are described below. + .. index:: single: imperative configuration @@ -31,8 +35,9 @@ itself. When you plug code into the :app:`Pyramid` framework, you are Imperative Configuration ------------------------ -Here's one of the simplest :app:`Pyramid` applications, configured -imperatively: +"Imperative configuration" just means configuration done by Python +statements, one after the next. Here's one of the simplest :app:`Pyramid` +applications, configured imperatively: .. code-block:: python :linenos: @@ -64,19 +69,17 @@ including conditionals, can be employed in this mode of configuration. .. _decorations_and_code_scanning: -Configuration Decorations and Code Scanning -------------------------------------------- +Declarative Configuration +------------------------- -A different mode of configuration gives more *locality of reference* to a -:term:`configuration declaration`. It's sometimes painful to have all -configuration done in imperative code, because often the code for a single -application may live in many files. If the configuration is centralized in -one place, you'll need to have at least two files open at once to see the -"big picture": the file that represents the configuration, and the file that -contains the implementation objects referenced by the configuration. To -avoid this, :app:`Pyramid` allows you to insert :term:`configuration -decoration` statements very close to code that is referred to by the -declaration itself. For example: +It's sometimes painful to have all configuration done by imperative code, +because often the code for a single application may live in many files. If +the configuration is centralized in one place, you'll need to have at least +two files open at once to see the "big picture": the file that represents the +configuration, and the file that contains the implementation objects +referenced by the configuration. To avoid this, :app:`Pyramid` allows you to +insert :term:`configuration decoration` statements very close to code that is +referred to by the declaration itself. For example: .. code-block:: python :linenos: @@ -135,6 +138,9 @@ the scanner, a set of calls are made to a :term:`Configurator` on your behalf: these calls replace the need to add imperative configuration statements that don't live near the code being configured. +The combination of :term:`configuration decoration` and the invocation of a +:term:`scan` is collectively known as :term:`declarative configuration`. + In the example above, the scanner translates the arguments to :class:`~pyramid.view.view_config` into a call to the :meth:`pyramid.config.Configurator.add_view` method, effectively: @@ -145,13 +151,3 @@ In the example above, the scanner translates the arguments to config.add_view(hello) -Declarative Configuration -------------------------- - -A third mode of configuration can be employed when you create a -:app:`Pyramid` application named *declarative configuration*. This mode uses -an XML language known as :term:`ZCML` to represent configuration statements -rather than Python. ZCML is not built-in to Pyramid, but almost everything -that can be configured imperatively can also be configured via ZCML if you -install the :term:`pyramid_zcml` package. - -- cgit v1.2.3 From 0784123a4042353beedc84960bbbf51068eec295 Mon Sep 17 00:00:00 2001 From: Manuel Hermann Date: Mon, 11 Jul 2011 16:47:38 +0200 Subject: Decorator version of config.add_response_adapter. --- docs/api/response.rst | 5 ++++ docs/narr/hooks.rst | 17 ++++++++++- pyramid/response.py | 56 ++++++++++++++++++++++++++++++++++++ pyramid/tests/test_response.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) diff --git a/docs/api/response.rst b/docs/api/response.rst index e67b15568..8020b629a 100644 --- a/docs/api/response.rst +++ b/docs/api/response.rst @@ -9,3 +9,8 @@ :members: :inherited-members: +Functions +~~~~~~~~~ + +.. autofunction:: response_adapter + diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 94701c9f9..0dcbcd371 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -536,7 +536,8 @@ Changing How Pyramid Treats View Responses It is possible to control how Pyramid treats the result of calling a view callable on a per-type basis by using a hook involving -:meth:`pyramid.config.Configurator.add_response_adapter`. +:meth:`pyramid.config.Configurator.add_response_adapter` or the +:class:`~pyramid.response.response_adapter` decorator. .. note:: This feature is new as of Pyramid 1.1. @@ -573,6 +574,20 @@ Response: config.add_response_adapter(string_response_adapter, str) +The above example using the :class:`~pyramid.response.response_adapter` +decorator: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(str) + def string_response_adapter(s): + response = Response(s) + return response + Likewise, if you want to be able to return a simplified kind of response object from view callables, you can use the IResponse hook to register an adapter to the more complex IResponse interface: diff --git a/pyramid/response.py b/pyramid/response.py index 68496e386..60666bd03 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -1,3 +1,5 @@ +import venusian + from webob import Response as _Response from zope.interface import implements from pyramid.interfaces import IResponse @@ -5,3 +7,57 @@ from pyramid.interfaces import IResponse class Response(_Response): implements(IResponse) + +class response_adapter(object): + """ Decorator activated via a :term:`scan` which treats the + function being decorated as a response adapter for the set of types or + interfaces passed as ``*types_or_ifaces`` to the decorator constructor. + + For example: + + .. code-block:: python + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(int) + def myadapter(i): + return Response(status=i) + + More than one type or interface can be passed as a constructor argument. + The decorated response adapter will be called for each type or interface. + + .. code-block:: python + + import json + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(dict, list) + def myadapter(ob): + return Response(json.dumps(ob)) + + This method will have no effect until a :term:`scan` is performed + agains the package or module which contains it, ala: + + .. code-block:: python + + from pyramid.config import Configurator + config = Configurator() + config.scan('somepackage_containing_adapters') + + """ + venusian = venusian # for unit testing + + def __init__(self, *types_or_ifaces): + self.types_or_ifaces = types_or_ifaces + + def register(self, scanner, name, wrapped): + config = scanner.config + for type_or_iface in self.types_or_ifaces: + config.add_response_adapter(wrapped, type_or_iface) + + def __call__(self, wrapped): + self.venusian.attach(wrapped, self.register, category='pyramid') + return wrapped diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index 6cb2fd6f4..39360c0af 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -1,4 +1,5 @@ import unittest +from pyramid import testing class TestResponse(unittest.TestCase): def _getTargetClass(self): @@ -15,3 +16,66 @@ class TestResponse(unittest.TestCase): inst = self._getTargetClass()() self.assertTrue(IResponse.providedBy(inst)) +class Dummy(object): + pass + +class DummyConfigurator(object): + def __init__(self): + self.adapters = [] + + def add_response_adapter(self, wrapped, type_or_iface): + self.adapters.append((wrapped, type_or_iface)) + +class DummyVenusian(object): + def __init__(self): + self.attached = [] + + def attach(self, wrapped, fn, category=None): + self.attached.append((wrapped, fn, category)) + +class TestResponseAdapter(unittest.TestCase): + def setUp(self): + registry = Dummy() + self.config = testing.setUp(registry=registry) + self.config.begin() + + def tearDown(self): + self.config.end() + + def _makeOne(self, *types_or_ifaces): + from pyramid.response import response_adapter + return response_adapter(*types_or_ifaces) + + def test_register_single(self): + from zope.interface import Interface + class IFoo(Interface): pass + dec = self._makeOne(IFoo) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.adapters, [(foo, IFoo)]) + + def test_register_multi(self): + from zope.interface import Interface + class IFoo(Interface): pass + class IBar(Interface): pass + dec = self._makeOne(IFoo, IBar) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.adapters, [(foo, IFoo), (foo, IBar)]) + + def test___call__(self): + from zope.interface import Interface + class IFoo(Interface): pass + dec = self._makeOne(IFoo) + dummy_venusian = DummyVenusian() + dec.venusian = dummy_venusian + def foo(): pass + dec(foo) + self.assertEqual(dummy_venusian.attached, + [(foo, dec.register, 'pyramid')]) -- cgit v1.2.3 From 1d38fdf1d58af73f224f4c92dbbc90fb1803ca64 Mon Sep 17 00:00:00 2001 From: Manuel Hermann Date: Mon, 11 Jul 2011 16:59:07 +0200 Subject: Sign the contribution agreement. --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index cdc011f87..c2851ffd2 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -142,3 +142,5 @@ Contributors - Michael Merickel, 2011/5/25 - Christoph Zwerschke, 2011/06/07 + +- Manuel Hermann, 2011/07/11 -- cgit v1.2.3 From e3fabe0bca3579b59a2118d3b1662bfdb282f82a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 14:30:48 -0400 Subject: give request a registry = None clasattr so registry can successfully be passed as a kwarg to the Request constructor --- pyramid/request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyramid/request.py b/pyramid/request.py index 1bf044b69..65460444b 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -206,6 +206,7 @@ class Request(BaseRequest, DeprecatedRequestMethods): exception = None matchdict = None matched_route = None + registry = None @reify def tmpl_context(self): -- cgit v1.2.3 From 64d1fde03ad69d5fcdb15908b1701d6765779e0f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 14:34:41 -0400 Subject: remove request clasattr, it breaks tests --- pyramid/request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/request.py b/pyramid/request.py index 65460444b..1bf044b69 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -206,7 +206,6 @@ class Request(BaseRequest, DeprecatedRequestMethods): exception = None matchdict = None matched_route = None - registry = None @reify def tmpl_context(self): -- cgit v1.2.3 From 0388722bcfee9fce6b241264ea4535a8d0d5fd8c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:35:20 -0400 Subject: we no longer support 24. --- docs/narr/install.rst | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index fe8459c6f..c4ea978ec 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -9,17 +9,14 @@ Installing :app:`Pyramid` Before You Install ------------------ -You will need `Python `_ version 2.4 or better to +You will need `Python `_ version 2.5 or better to run :app:`Pyramid`. .. sidebar:: Python Versions - As of this writing, :app:`Pyramid` has been tested under Python - 2.4.6, Python 2.5.4 and Python 2.6.2, and Python 2.7. To ensure - backwards compatibility, development of :app:`Pyramid` is - currently done primarily under Python 2.4 and Python 2.5. - :app:`Pyramid` does not run under any version of Python before - 2.4, and does not yet run under Python 3.X. + As of this writing, :app:`Pyramid` has been tested under Python 2.5.5 and + Python 2.6.6, and Python 2.7.2. :app:`Pyramid` does not run under any + version of Python before 2.5, and does not yet run under Python 3.X. :app:`Pyramid` is known to run on all popular Unix-like systems such as Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also @@ -143,15 +140,15 @@ setuptools`` within the Python interpreter you'd like to run .. code-block:: text [chrism@vitaminf pyramid]$ python - Python 2.4.5 (#1, Aug 29 2008, 12:27:37) - [GCC 4.0.1 (Apple Inc. build 5465)] on darwin + Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) + [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import setuptools If running ``import setuptools`` does not raise an ``ImportError``, it means that setuptools is already installed into your Python interpreter. If ``import setuptools`` fails, you will need to install -setuptools manually. Note that above we're using a Python 2.4-series +setuptools manually. Note that above we're using a Python 2.6-series interpreter on Mac OS X; your output may differ if you're using a later Python version or a different platform. -- cgit v1.2.3 From 7a505dbfc9ae9a5c933072653ac44e4086d26e59 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:36:36 -0400 Subject: simplify --- docs/narr/introduction.rst | 82 ++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index a0b682e25..d26f1b8bf 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -10,11 +10,8 @@ ============================== :app:`Pyramid` is a general, open source, Python web application development -*framework*. Its primary goal is to make it easier for a developer to create -web applications. The type of application being created could be a -spreadsheet, a corporate intranet, or a social networking platform; Pyramid's -generality enables it to be used to build an unconstrained variety of web -applications. +*framework*. Its primary goal is to make it easier for a Python developer to +create web applications. .. sidebar:: Frameworks vs. Libraries @@ -33,34 +30,30 @@ applications. own via a set of libraries if the framework provides a set of facilities that fits your application requirements. -The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made -in July of 2008. We have worked hard to ensure that Pyramid continues to -follow the design and engineering principles that we consider to be the core -characteristics of a successful framework: +Pyramid attempts to follow follow these design and engineering principles: Simplicity - :app:`Pyramid` takes a *"pay only for what you eat"* approach. This means - that you can get results even if you have only a partial understanding of - :app:`Pyramid`. It doesn’t force you to use any particular technology to - produce an application, and we try to keep the core set of concepts that - you need to understand to a minimum. + :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get + results even if you have only a partial understanding of :app:`Pyramid`. + It doesn’t force you to use any particular technology to produce an + application, and we try to keep the core set of concepts that you need to + understand to a minimum. Minimalism - :app:`Pyramid` concentrates on providing fast, high-quality solutions to - the fundamental problems of creating a web application: the mapping of URLs - to code, templating, security and serving static assets. We consider these - to be the core activities that are common to nearly all web applications. + :app:`Pyramid` tries to solve only the the fundamental problems of creating + a web application: the mapping of URLs to code, templating, security and + serving static assets. We consider these to be the core activities that are + common to nearly all web applications. Documentation - Pyramid's minimalism means that it is relatively easy for us to maintain - extensive and up-to-date documentation. It is our goal that no aspect of - Pyramid remains undocumented. + Pyramid's minimalism means that it is easier for us to maintain complete + and up-to-date documentation. It is our goal that no aspect of Pyramid + is undocumented. Speed :app:`Pyramid` is designed to provide noticeably fast execution for common - tasks such as templating and simple response generation. Although the - “hardware is cheap” mantra may appear to offer a ready solution to speed - problems, the limits of this approach become painfully evident when one + tasks such as templating and simple response generation. Although “hardware + is cheap", the limits of this approach become painfully evident when one finds him or herself responsible for managing a great many machines. Reliability @@ -75,7 +68,6 @@ Openness .. index:: single: Pylons - single: Agendaless Consulting single: repoze namespace package What Is The Pylons Project? @@ -83,7 +75,7 @@ What Is The Pylons Project? :app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of -contributors. The `Pylons Project website `_ +contributors. The `Pylons Project website `_ includes details about how :app:`Pyramid` relates to the Pylons Project. .. index:: @@ -96,23 +88,22 @@ includes details about how :app:`Pyramid` relates to the Pylons Project. :app:`Pyramid` and Other Web Frameworks ------------------------------------------ -Until the end of 2010, :app:`Pyramid` was known as :mod:`repoze.bfg`; it was -merged into the Pylons project as :app:`Pyramid` in November of that year. +The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made +in July of 2008. At the end of 2010, we changed the name of +:mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project +as :app:`Pyramid` in November of that year. :app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0) and :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and features from each, combining them into a unique web framework. -Many features of :app:`Pyramid` trace their origins back to -:term:`Zope`. Like Zope applications, :app:`Pyramid` applications -can be configured via a set of declarative configuration files. Like -Zope applications, :app:`Pyramid` applications can be easily -extended: if you obey certain constraints, the application you produce -can be reused, modified, re-integrated, or extended by third-party -developers without forking the original application. The concepts of -:term:`traversal` and declarative security in :app:`Pyramid` were -pioneered first in Zope. +Many features of :app:`Pyramid` trace their origins back to :term:`Zope`. +Like Zope applications, :app:`Pyramid` applications can be easily extended: +if you obey certain constraints, the application you produce can be reused, +modified, re-integrated, or extended by third-party developers without +forking the original application. The concepts of :term:`traversal` and +declarative security in :app:`Pyramid` were pioneered first in Zope. The :app:`Pyramid` concept of :term:`URL dispatch` is inspired by the :term:`Routes` system used by :term:`Pylons` version 1.0. Like Pylons @@ -127,15 +118,14 @@ The concept of :term:`view` is used by :app:`Pyramid` mostly as it would be by Django. :app:`Pyramid` has a documentation culture more like Django's than like Zope's. -Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a -:app:`Pyramid` application developer may use completely imperative -code to perform common framework configuration tasks such as adding a -view or a route. In Zope, :term:`ZCML` is typically required for -similar purposes. In :term:`Grok`, a Zope-based web framework, -:term:`decorator` objects and class-level declarations are used for -this purpose. :app:`Pyramid` supports :term:`ZCML` and -decorator-based configuration, but does not require either. See -:ref:`configuration_narr` for more information. +Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a :app:`Pyramid` +application developer may use completely imperative code to perform common +framework configuration tasks such as adding a view or a route. In Zope, +:term:`ZCML` is typically required for similar purposes. In :term:`Grok`, a +Zope-based web framework, :term:`decorator` objects and class-level +declarations are used for this purpose. :app:`Pyramid` supports :term:`ZCML` +and decorator-based :term:`declarative configuration`, but does not require +either. See :ref:`configuration_narr` for more information. Also unlike :term:`Zope` and unlike other "full-stack" frameworks such as :term:`Django`, :app:`Pyramid` makes no assumptions about which -- cgit v1.2.3 From 3cf66ad31a2e76d94421378660174b4058576c10 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:36:44 -0400 Subject: bad reference --- docs/glossary.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 3fa2b0261..bb1ea12ec 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -593,8 +593,8 @@ Glossary declarative configuration The configuration mode in which you use the combination of - :term:`configuration decorator` decorations and a :term:`scan` to - configure your Pyramid application. + :term:`configuration decoration` and a :term:`scan` to configure your + Pyramid application. Not Found view An :term:`exception view` invoked by :app:`Pyramid` when the -- cgit v1.2.3 From f55b54a16def0bb0c463ee302dd12eefaa3638ad Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:37:01 -0400 Subject: convenience command --- docs/remake | 1 + 1 file changed, 1 insertion(+) create mode 100755 docs/remake diff --git a/docs/remake b/docs/remake new file mode 100755 index 000000000..b236f2976 --- /dev/null +++ b/docs/remake @@ -0,0 +1 @@ +make clean html SPHINXBUILD=../env26/bin/sphinx-build -- cgit v1.2.3 From f98925f8b05d16114a13c29d03ebc947b6ad084c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:43:12 -0400 Subject: add PyPy --- docs/glossary.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index bb1ea12ec..cc1d6201d 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -908,3 +908,7 @@ Glossary A :term:`response` that is generated as the result of a raised exception being caught by an :term:`exception view`. + PyPy + PyPy is an "alternative implementation of the Python + language":http://pypy.org/ + -- cgit v1.2.3 From f05c3819cc8bfa1dd829a24f3c8cc82c4094a6cd Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:43:24 -0400 Subject: add PyPy --- docs/narr/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index c4ea978ec..837db5a94 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -20,7 +20,7 @@ run :app:`Pyramid`. :app:`Pyramid` is known to run on all popular Unix-like systems such as Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also -known to run on Google's App Engine and :term:`Jython`. +known to run on Google's App Engine, :term:`PyPy`, and :term:`Jython`. :app:`Pyramid` installation does not require the compilation of any C code, so you need only a Python interpreter that meets the -- cgit v1.2.3 From 7c48ff7bde21fda813ce74a7691c89bedd2b54cc Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 11 Jul 2011 23:58:57 -0400 Subject: simplify --- docs/narr/configuration.rst | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 4c2870562..3ecb4b06a 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -6,22 +6,15 @@ Application Configuration ========================= -Each deployment of an application written using :app:`Pyramid` implies a -specific *configuration* of the framework itself. For example, an -application which serves up MP3 files for your listening enjoyment might plug -code into the framework that manages song files, while an application that -manages corporate data might plug in code that manages accounting -information. The way in which code is plugged in to :app:`Pyramid` for a -specific application is referred to as "configuration". - -Most people understand "configuration" as coarse settings that inform the -high-level operation of a specific application deployment. For instance, -it's easy to think of the values implied by a ``.ini`` file parsed at -application startup time as "configuration". :app:`Pyramid` extends this -pattern to application development, using the term "configuration" to express -standardized ways that code gets plugged into a deployment of the framework -itself. When you plug code into the :app:`Pyramid` framework, you are -"configuring" :app:`Pyramid` to create a particular application. +The way in which code is plugged in to :app:`Pyramid` for a specific +application is referred to as "configuration". Most people understand +"configuration" as coarse settings that inform the high-level operation of a +specific application deployment. For instance, it's easy to think of the +values implied by a ``.ini`` file parsed at application startup time as +"configuration". However, :app:`Pyramid` also uses the word "configuration" +to express standardized ways that code gets plugged into a deployment of the +framework itself. When you plug code into the :app:`Pyramid` framework, you +are "configuring" :app:`Pyramid` to create a particular application. There are two ways to configure a :app:`Pyramid` application: :term:`imperative configuration` and :term:`declarative configuration`. Both -- cgit v1.2.3 From 8027d1621e859f44e94a063c7b87c818d9096c85 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:01:30 -0400 Subject: remove unnecessary clause --- docs/narr/firstapp.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index f5adad905..87487b444 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -12,8 +12,7 @@ more detail how it works. Hello World, Goodbye World -------------------------- -Here's one of the very simplest :app:`Pyramid` applications, configured -imperatively: +Here's one of the very simplest :app:`Pyramid` applications: .. code-block:: python :linenos: -- cgit v1.2.3 From f4a80417c886938dec83071a4d62238c78bf8810 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:10:54 -0400 Subject: add docs about logging config --- docs/narr/project.rst | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index ab7023561..4a7f63176 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -12,9 +12,9 @@ A project is a directory that contains at least one Python :term:`package`. You'll use a scaffold to create a project, and you'll create your application logic within a package that lives inside the project. Even if your application is extremely simple, it is useful to place code that drives the -application within a package, because a package is more easily extended with -new code. An application that lives inside a package can also be distributed -more easily than one which does not live within a package. +application within a package, because: 1) a package is more easily extended +with new code and 2) an application that lives inside a package can also be +distributed more easily than one which does not live within a package. :app:`Pyramid` comes with a variety of scaffolds that you can use to generate a project. Each scaffold makes different configuration assumptions about @@ -559,7 +559,8 @@ The generated ``development.ini`` file looks like so: :linenos: This file contains several "sections" including ``[app:MyProject]``, -``[pipeline:main]``, and ``[server:main]``. +``[pipeline:main]``, ``[server:main]`` and several other sections related to +logging configuration. The ``[app:MyProject]`` section represents configuration for your application. This section name represents the ``MyProject`` application (and @@ -643,6 +644,16 @@ for each request. application be nonblocking as all application code will run in its own thread, provided by the server you're using. +The sections that live between the markers ``# Begin logging configuration`` +and ``# End logging configuration`` represent Python's standard library +:mod:`logging` module configuration for your application. The sections +between these two markers are passed to the `logging module's config file +configuration engine +`_ when the +``paster serve`` or ``paster pshell`` commands are executed. The default +configuration sends application logging output to the standard error output +of your terminal. + See the :term:`PasteDeploy` documentation for more information about other types of things you can put into this ``.ini`` file, such as other applications, :term:`middleware` and alternate :term:`WSGI` server -- cgit v1.2.3 From 46ad10d4ea3f57c19d7ce54a1539a66d7ed621d2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:13:38 -0400 Subject: add docs about logging config --- docs/narr/startup.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index e2c43b17e..788896de9 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -53,6 +53,10 @@ Here's a high-level time-ordered overview of what happens when you press that particular composite to understand how to make it refer to your :app:`Pyramid` application. +#. The PasteDeploy framework finds all :mod:`logging` related configuration + in the ``.ini`` file and uses it to configure the Python standard library + logging system for this application. + #. The application's *constructor* (named by the entry point reference or dotted Python name on the ``use=`` line of the section representing your :app:`Pyramid` application) is passed the key/value parameters mentioned -- cgit v1.2.3 From 7278cf91ea041222f3af814d19e3d1c2f0536ba0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:28:09 -0400 Subject: remove deprecated mechanism to associate views with routes; add description of scan mechanism --- docs/narr/urldispatch.rst | 113 ++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 51a840b8d..8e2d240fa 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -12,10 +12,10 @@ patterns is checked one-by-one. If one of the patterns matches the path information associated with a request, a particular :term:`view callable` is invoked. -:term:`URL dispatch` is one of two ways to perform :term:`resource -location` in :app:`Pyramid`; the other way is using :term:`traversal`. -If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls -back to :term:`traversal` to handle the :term:`request`. +:term:`URL dispatch` is one of two ways to perform :term:`resource location` +in :app:`Pyramid`; the other way is to use :term:`traversal`. If no route is +matched using :term:`URL dispatch`, :app:`Pyramid` falls back to +:term:`traversal` to handle the :term:`request`. It is the responsibility of the :term:`resource location` subsystem (i.e., :term:`URL dispatch` or :term:`traversal`) to find the resource @@ -67,8 +67,8 @@ attributes. .. _config-add-route: -Configuring a Route via The ``add_route`` Configurator Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configuring a Route to Match a View +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_route` method adds a single :term:`route configuration` to the :term:`application registry`. Here's an @@ -84,90 +84,45 @@ example: config.add_route('myroute', '/prefix/{one}/{two}') config.add_view(myview, route_name='myroute') -.. versionchanged:: 1.0a4 - Prior to 1.0a4, routes allow for a marker starting with a ``:``, for - example ``/prefix/:one/:two``. This style is now deprecated - in favor of ``{}`` usage which allows for additional functionality. +When a :term:`view callable` added to the configuration by way of +:meth:`~pyramid.config.Configurator.add_view` bcomes associated with a route +via its ``route_name`` predicate, that view callable will always be found +and invoked when the associated route pattern matches during a request. -.. index:: - single: route configuration; view callable - -.. _add_route_view_config: - -Route Configuration That Names a View Callable -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. warning:: This section describes a feature which has been deprecated in - Pyramid 1.1 and higher. In order to reduce confusion and documentation - burden, passing view-related parameters to - :meth:`~pyramid.config.Configurator.add_route` is deprecated. - - In versions earlier than 1.1, a view was permitted to be connected to a - route using a set of ``view*`` parameters passed to the - :meth:`~pyramid.config.Configurator.add_route`. This was a shorthand - which replaced the need to perform a subsequent call to - :meth:`~pyramid.config.Configurator.add_view` as described in - :ref:`config-add-route`. For example, it was valid (and often recommended) - to do: - - .. code-block:: python +More commonly, you will not use any ``add_view`` statements in your project's +"setup" code, instead only using ``add_route`` statements using a +:term:`scan` for to associate view callables with routes. For example, if +this is a portion of your project's ``__init__.py``: - config.add_route('home', '/', view='mypackage.views.myview', - view_renderer='some/renderer.pt') - - Instead of the equivalent: - - .. code-block:: python - - config.add_route('home', '/') - config.add_view('mypackage.views.myview', route_name='home') - renderer='some/renderer.pt') - - Passing ``view*`` arguments to ``add_route`` as shown in the first - example above is now deprecated in favor of connecting a view to a - predefined route via :meth:`~pyramid.config.Configurator.add_view` using - the route's ``route_name`` parameter, as shown in the second example - above. +.. code-block:: python - A deprecation warning is now issued when any view-related parameter is - passed to ``Configurator.add_route``. The recommended way to associate a - view with a route is documented in :ref:`config-add-route`. + # in your project's __init__.py (mypackage.__init__) -When a route configuration declaration names a ``view`` attribute, the value -of the attribute will reference a :term:`view callable`. This view callable -will be invoked when the route matches. A view callable, as described in -:ref:`views_chapter`, is developer-supplied code that "does stuff" as the -result of a request. + config.add_route('myroute', '/prefix/{one}/{two}') + config.scan('mypackage') -Here's an example route configuration that references a view callable: +Note that we don't call :meth:`~pyramid.config.Configurator.add_view` in this +setup code. However, the above :term:`scan` execution +``config.scan('mypackage')`` will pick up all :term:`configuration +decoration`, including any objects decorated with the +:class:`pyramid.view.view_config` decorator in the ``mypackage`` Python +pakage. For example, if you have a ``views.py`` in your package, a scan will +pick up any of its configuration decorators, so we can add one there that +that references ``myroute`` as a ``route_name`` parameter: .. code-block:: python - :linenos: - - # "config" below is presumed to be an instance of the - # pyramid.config.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) - -You can also pass a :term:`dotted Python name` as the ``view`` argument -rather than an actual callable: -.. code-block:: python - :linenos: + # in your project's views.py module (mypackage.views) - # "config" below is presumed to be an instance of the - # pyramid.config.Configurator class; "myview" is assumed - # to be a "view callable" function - config.add_route('myroute', '/prefix/{one}/{two}', - view='myproject.views.myview') + from pyramid.view import view_config + from pyramid.response import Response -When a route configuration names a ``view`` attribute, the :term:`view -callable` named as that ``view`` attribute will always be found and invoked -when the associated route pattern matches during a request. + @view_config(route_name='myroute') + def myview(request): + return Response('OK) -See :meth:`pyramid.config.Configurator.add_route` for a description of -view-related arguments. +THe above combination of ``add_route`` and ``scan`` is completely equivalent +to using the previous combination of ``add_route`` and ``add_view``. .. index:: single: route path pattern syntax -- cgit v1.2.3 From 321e34669f0b9119894cd1ee8c115361deba29f6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:33:56 -0400 Subject: remove references to add_route view-related configuration --- docs/narr/urldispatch.rst | 9 +++++---- pyramid/config.py | 19 +++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 8e2d240fa..779a884fb 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -394,13 +394,14 @@ the associated route to be considered a match during the route matching process. Examples of route predicate arguments are ``pattern``, ``xhr``, and ``request_method``. -Other arguments are view configuration related arguments. These only have an -effect when the route configuration names a ``view``. These arguments have -been deprecated as of :app:`Pyramid` 1.1 (see :ref:`add_route_view_config`). - Other arguments are ``name`` and ``factory``. These arguments represent neither predicates nor view configuration information. +.. warning:: Some arguments are view-configuration related arguments, such as + ``view_renderer``. These only have an effect when the route configuration + names a ``view`` and these arguments have been deprecated as of + :app:`Pyramid` 1.1. + .. _custom_route_predicates: Custom Route Predicates diff --git a/pyramid/config.py b/pyramid/config.py index 2e018f66f..8740d8448 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1801,12 +1801,11 @@ class Configurator(object): should only be used to support older code bases which depend upon them.* Use a separate call to :meth:`pyramid.config.Configurator.add_view` to associate a view - with a route. See :ref:`add_route_view_config` for more info. + with a route using the ``route_name`` argument. view - .. warning:: Deprecated as of :app:`Pyramid` 1.1; see - :ref:`add_route_view_config`. + .. warning:: Deprecated as of :app:`Pyramid` 1.1. A Python object or :term:`dotted Python name` to the same object that will be used as a view callable when this route @@ -1814,9 +1813,8 @@ class Configurator(object): view_context - .. warning:: Deprecated as of :app:`Pyramid` 1.1; see - :ref:`add_route_view_config`. - + .. warning:: Deprecated as of :app:`Pyramid` 1.1. + A class or an :term:`interface` or :term:`dotted Python name` to the same object which the :term:`context` of the view should match for the view named by the route to be @@ -1831,8 +1829,7 @@ class Configurator(object): view_permission - .. warning:: Deprecated as of :app:`Pyramid` 1.1; see - :ref:`add_route_view_config`. + .. warning:: Deprecated as of :app:`Pyramid` 1.1. The permission name required to invoke the view associated with this route. e.g. ``edit``. (see @@ -1846,8 +1843,7 @@ class Configurator(object): view_renderer - .. warning:: Deprecated as of :app:`Pyramid` 1.1; see - :ref:`add_route_view_config`. + .. warning:: Deprecated as of :app:`Pyramid` 1.1. This is either a single string term (e.g. ``json``) or a string implying a path or :term:`asset specification` @@ -1871,8 +1867,7 @@ class Configurator(object): view_attr - .. warning:: Deprecated as of :app:`Pyramid` 1.1; see - :ref:`add_route_view_config`. + .. warning:: Deprecated as of :app:`Pyramid` 1.1. The view machinery defaults to using the ``__call__`` method of the view callable (or the function itself, if the view -- cgit v1.2.3 From e4770d391bd87c5d6164affb1a2f79b77b35b58b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 00:36:12 -0400 Subject: fix sample --- docs/narr/urldispatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 779a884fb..52311682e 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -119,7 +119,7 @@ that references ``myroute`` as a ``route_name`` parameter: @view_config(route_name='myroute') def myview(request): - return Response('OK) + return Response('OK') THe above combination of ``add_route`` and ``scan`` is completely equivalent to using the previous combination of ``add_route`` and ``add_view``. -- cgit v1.2.3 From 7cf06765c863ec73adccc73cefcf7630b2020f21 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 03:26:21 -0400 Subject: warn about warnings in 2.7+ --- docs/whatsnew-1.1.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index b249b0ba7..8cf6c715c 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -308,6 +308,15 @@ Backwards Incompatibilities Deprecations and Behavior Differences ------------------------------------- +.. note:: Under Python 2.7+, it's necessary to pass the Python interpreter + the correct warning flags to see deprecation warnings emitted by Pyramid + when porting your application from an older version of Pyramid. Use the + ``PYTHONWARNINGS`` environment variable with the value ``all`` in the + shell you use to invoke ``paster serve`` to see these warnings, e.g. on + UNIX, ``PYTHONWARNINGS=all bin/paster serve development.ini``. Python 2.5 + and 2.6 show deprecation warnings by default, so this is unecessary there. + All deprecation warnings are emitted to the console. + - The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands now take a single argument in the form ``/path/to/config.ini#sectionname`` rather than the previous 2-argument spelling ``/path/to/config.ini -- cgit v1.2.3 From f9896b60700ea4334f3df0a83f9b291e167b322d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 03:31:08 -0400 Subject: reviewed --- TODO.txt | 3 --- docs/narr/hooks.rst | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index ee5275952..a28ab8800 100644 --- a/TODO.txt +++ b/TODO.txt @@ -10,9 +10,6 @@ Must-Have (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in tutorial) -- deprecate request.add_response_callback (or at least review docs, in light - of request.response property). - Should-Have ----------- diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 94701c9f9..a156426ae 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -284,8 +284,8 @@ Using Response Callbacks Unlike many other web frameworks, :app:`Pyramid` does not eagerly create a global response object. Adding a :term:`response callback` allows an -application to register an action to be performed against a response object -once it is created, usually in order to mutate it. +application to register an action to be performed against whatever response +object is returned by a view, usually in order to mutate the response. The :meth:`pyramid.request.Request.add_response_callback` method is used to register a response callback. -- cgit v1.2.3 From 82efa44c0d8f4b18b4f341519f54ecad68b56364 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 03:52:42 -0400 Subject: - Previously, If a ``BeforeRender`` event subscriber added a value via the ``__setitem__`` or ``update`` methods of the event object with a key that already existed in the renderer globals dictionary, a ``KeyError`` was raised. With the deprecation of the "add_renderer_globals" feature of the configurator, there was no way to override an existing value in the renderer globals dictionary that already existed. Now, the event object will overwrite an older value that is already in the globals dictionary when its ``__setitem__`` or ``update`` is called (as well as the new ``setdefault`` method), just like a plain old dictionary. As a result, for maximum interoperability with other third-party subscribers, if you write an event subscriber meant to be used as a BeforeRender subscriber, your subscriber code will now need to (using ``.get`` or ``__contains__`` of the event object) ensure no value already exists in the renderer globals dictionary before setting an overriding value. --- CHANGES.txt | 21 +++++++++++++++++++++ docs/whatsnew-1.1.rst | 16 ++++++++++++++++ pyramid/events.py | 45 +++++++++++++++++++++++++------------------- pyramid/interfaces.py | 13 +++++++++---- pyramid/tests/test_events.py | 21 +++++++++++++-------- 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2dead04b4..b9e645a38 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,24 @@ +Next Release +============ + +Behavior Changes +---------------- + +- Previously, If a ``BeforeRender`` event subscriber added a value via the + ``__setitem__`` or ``update`` methods of the event object with a key that + already existed in the renderer globals dictionary, a ``KeyError`` was + raised. With the deprecation of the "add_renderer_globals" feature of the + configurator, there was no way to override an existing value in the + renderer globals dictionary that already existed. Now, the event object + will overwrite an older value that is already in the globals dictionary + when its ``__setitem__`` or ``update`` is called (as well as the new + ``setdefault`` method), just like a plain old dictionary. As a result, for + maximum interoperability with other third-party subscribers, if you write + an event subscriber meant to be used as a BeforeRender subscriber, your + subscriber code will now need to (using ``.get`` or ``__contains__`` of the + event object) ensure no value already exists in the renderer globals + dictionary before setting an overriding value. + 1.1b1 (2011-07-10) ================== diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 8cf6c715c..345cbfa30 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -481,6 +481,22 @@ Deprecations and Behavior Differences def expects_object_event(object, event): print object, event +- In 1.0, if a :class:`pyramid.events.BeforeRender` event subscriber added a + value via the ``__setitem__`` or ``update`` methods of the event object + with a key that already existed in the renderer globals dictionary, a + ``KeyError`` was raised. With the deprecation of the + "add_renderer_globals" feature of the configurator, there was no way to + override an existing value in the renderer globals dictionary that already + existed. Now, the event object will overwrite an older value that is + already in the globals dictionary when its ``__setitem__`` or ``update`` is + called (as well as the new ``setdefault`` method), just like a plain old + dictionary. As a result, for maximum interoperability with other + third-party subscribers, if you write an event subscriber meant to be used + as a BeforeRender subscriber, your subscriber code will now need to (using + ``.get`` or ``__contains__`` of the event object) ensure no value already + exists in the renderer globals dictionary before setting an overriding + value. + Dependency Changes ------------------ diff --git a/pyramid/events.py b/pyramid/events.py index 22cbf0cb2..b536bfd67 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -179,35 +179,42 @@ class BeforeRender(dict): event['mykey'] = 'foo' An object of this type is sent as an event just before a :term:`renderer` - is invoked (but *after* the application-level renderer globals factory - added via - :class:`pyramid.config.Configurator.set_renderer_globals_factory`, - if any, has injected its own keys into the renderer globals dictionary). - - If a subscriber attempts to add a key that already exist in the renderer - globals dictionary, a :exc:`KeyError` is raised. This limitation is - enforced because event subscribers do not possess any relative ordering. - The set of keys added to the renderer globals dictionary by all - :class:`pyramid.events.BeforeRender` subscribers and renderer globals - factories must be unique. """ + is invoked (but *after* the -- deprecated -- application-level renderer + globals factory added via + :class:`pyramid.config.Configurator.set_renderer_globals_factory`, if + any, has injected its own keys into the renderer globals dictionary). + + If a subscriber adds a key via ``__setitem__`` or that already exists in + the renderer globals dictionary, it will overwrite an older value that is + already in the globals dictionary. This can be problematic because event + subscribers to the BeforeRender event do not possess any relative + ordering. For maximum interoperability with other third-party + subscribers, if you write an event subscriber meant to be used as a + BeforeRender subscriber, your subscriber code will need to (using + ``.get`` or ``__contains__`` of the event object) ensure no value already + exists in the renderer globals dictionary before setting an overriding + value.""" def __init__(self, system): self._system = system def __setitem__(self, name, value): """ Set a name/value pair into the dictionary which is passed to a - renderer as the renderer globals dictionary. If the ``name`` already - exists in the target dictionary, a :exc:`KeyError` will be raised.""" - if name in self._system: - raise KeyError('%s is already a renderer globals value' % name) + renderer as the renderer globals dictionary.""" self._system[name] = value + def setdefault(self, name, default=None): + """ Return the existing value for ``name`` in the renderers globals + dictionary. If no value with ``name`` exists in the dictionary, set + the ``default`` value into the renderer globals dictionary under the + name passed. If a value already existed in the dictionary, return + it. If a value did not exist in the dictionary, return the default""" + return self._system.setdefault(name, default) + def update(self, d): """ Update the renderer globals dictionary with another dictionary - ``d``. If any of the key names in the source dictionary already exist - in the target dictionary, a :exc:`KeyError` will be raised""" - for k, v in d.items(): - self[k] = v + ``d``.""" + return self._system.update(d) def __contains__(self, k): """ Return ``True`` if ``k`` exists in the renderer globals diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index fee8d549d..4ef58846b 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -284,13 +284,18 @@ class IBeforeRender(Interface): """ def __setitem__(name, value): """ Set a name/value pair into the dictionary which is passed to a - renderer as the renderer globals dictionary. If the ``name`` already - exists in the target dictionary, a :exc:`KeyError` will be raised.""" + renderer as the renderer globals dictionary. """ + + def setdefault(name, default=None): + """ Return the existing value for ``name`` in the renderers globals + dictionary. If no value with ``name`` exists in the dictionary, set + the ``default`` value into the renderer globals dictionary under the + name passed. If a value already existed in the dictionary, return + it. If a value did not exist in the dictionary, return the default""" def update(d): """ Update the renderer globals dictionary with another dictionary - ``d``. If any of the key names in the source dictionary already exist - in the target dictionary, a :exc:`KeyError` will be raised""" + ``d``. """ def __contains__(k): """ Return ``True`` if ``k`` exists in the renderer globals diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 09f5f17ab..e3a10ccad 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -195,10 +195,20 @@ class TestBeforeRender(unittest.TestCase): event['a'] = 1 self.assertEqual(system, {'a':1}) - def test_setitem_fail(self): - system = {'a':1} + def test_setdefault_fail(self): + system = {} + event = self._makeOne(system) + result = event.setdefault('a', 1) + self.assertEqual(result, 1) + self.assertEqual(system, {'a':1}) + + def test_setdefault_success(self): + system = {} event = self._makeOne(system) - self.assertRaises(KeyError, event.__setitem__, 'a', 1) + event['a'] = 1 + result = event.setdefault('a', 2) + self.assertEqual(result, 1) + self.assertEqual(system, {'a':1}) def test_update_success(self): system = {'a':1} @@ -206,11 +216,6 @@ class TestBeforeRender(unittest.TestCase): event.update({'b':2}) self.assertEqual(system, {'a':1, 'b':2}) - def test_update_fail(self): - system = {'a':1} - event = self._makeOne(system) - self.assertRaises(KeyError, event.update, {'a':1}) - def test__contains__True(self): system = {'a':1} event = self._makeOne(system) -- cgit v1.2.3 From 9e189243d64f32ee33153e6f257e989d43a6b011 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 05:19:39 -0400 Subject: remove unused import --- pyramid/router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/router.py b/pyramid/router.py index d011b1245..880429424 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -12,7 +12,6 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IResponse from pyramid.events import ContextFound from pyramid.events import NewRequest -- cgit v1.2.3 From a7ad42ee5d6ba2304423efaf5fd0c207268144f0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 19:05:10 -0400 Subject: nope, it works... just easy to get frustrated and give up --- TODO.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index a28ab8800..637f7e49b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -16,9 +16,6 @@ Should-Have - Make "localizer" a property of request (instead of requiring "get_localizer(request)"? -- Investigate mod_wsgi tutorial to make sure it still works (2 reports say - no; application package not found). - - Add narrative docs for wsgiapp and wsgiapp2. Nice-to-Have -- cgit v1.2.3 From 94ab244f121fa1bf8df57f75fad9da5f5f39c594 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 19:21:11 -0400 Subject: - The ``Configurator.add_route`` method allowed two routes with the same route to be added without an intermediate ``config.commit()``. If you now receive a ``ConfigurationError`` at startup time that appears to be ``add_route`` related, you'll need to either a) ensure that all of your route names are unique or b) call ``config.commit()`` before adding a second route with the name of a previously added name or c) use a Configurator that works in ``autocommit`` mode. --- CHANGES.txt | 11 +++++++++++ TODO.txt | 2 -- docs/whatsnew-1.1.rst | 9 +++++++++ pyramid/config.py | 6 +----- pyramid/tests/test_config.py | 7 +++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b9e645a38..403969fc0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,17 @@ Behavior Changes event object) ensure no value already exists in the renderer globals dictionary before setting an overriding value. +Bug Fixes +--------- + +- The ``Configurator.add_route`` method allowed two routes with the same + route to be added without an intermediate ``config.commit()``. If you now + receive a ``ConfigurationError`` at startup time that appears to be + ``add_route`` related, you'll need to either a) ensure that all of your + route names are unique or b) call ``config.commit()`` before adding a + second route with the name of a previously added name or c) use a + Configurator that works in ``autocommit`` mode. + 1.1b1 (2011-07-10) ================== diff --git a/TODO.txt b/TODO.txt index 637f7e49b..4ff8689ce 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,8 +4,6 @@ Pyramid TODOs Must-Have --------- -- add_route discriminator wrong - - tutorial models.initialize_sql doesn't match scaffold (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in tutorial) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 345cbfa30..b55b30238 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -497,6 +497,15 @@ Deprecations and Behavior Differences exists in the renderer globals dictionary before setting an overriding value. +- The :meth:`pyramid.config.Configurator.add_route` method allowed two routes + with the same route to be added without an intermediate call to + :meth:`pyramid.config.Configurator.commit``. If you now receive a + ``ConfigurationError`` at startup time that appears to be ``add_route`` + related, you'll need to either a) ensure that all of your route names are + unique or b) call ``config.commit()`` before adding a second route with the + name of a previously added name or c) use a Configurator that works in + ``autocommit`` mode. + Dependency Changes ------------------ diff --git a/pyramid/config.py b/pyramid/config.py index 8740d8448..6efb6c00e 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1931,11 +1931,7 @@ class Configurator(object): if pattern is None: raise ConfigurationError('"pattern" argument may not be None') - discriminator = ['route', name, xhr, request_method, path_info, - request_param, header, accept] - discriminator.extend(sorted(custom_predicates)) - discriminator = tuple(discriminator) - + discriminator = ('route', name) self.action(discriminator, None) return mapper.connect(name, pattern, factory, predicates=predicates, diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index d7e62da0a..84848c2ad 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -1998,6 +1998,13 @@ class ConfiguratorTests(unittest.TestCase): self._assertRoute(config, 'name', 'path') self.assertEqual(route.name, 'name') + def test_add_route_discriminator(self): + config = self._makeOne() + route = config.add_route('name', 'path') + self._assertRoute(config, 'name', 'path') + self.assertEqual(route.name, 'name') + self.assertEqual(config._ctx.actions[-1][0], ('route', 'name')) + def test_add_route_with_factory(self): config = self._makeOne(autocommit=True) factory = object() -- cgit v1.2.3 From 9a064ea3fe62ab9c73573b022bc6381d04aa94ee Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 19:25:33 -0400 Subject: garden --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index 4ff8689ce..85b11ff06 100644 --- a/TODO.txt +++ b/TODO.txt @@ -16,6 +16,9 @@ Should-Have - Add narrative docs for wsgiapp and wsgiapp2. +- Symlink to current docs at docs.pylonsproject.org/curr and change all + scaffolds to use it instead of docs.pylonsproject.org/dev + Nice-to-Have ------------ -- cgit v1.2.3 From aec6b29b42ad2acf0c9febd884ae9db1316022c5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 19:52:26 -0400 Subject: - The ``pyramid_routesalchemy`` and ``pyramid_alchemy`` scaffolds inappropriately used ``DBSession.rollback()`` instead of ``transaction.abort()`` in one place. - Wiki2 (SQLAlchemy + URL Dispatch) tutorial ``models.initialize_sql`` didn't match the ``pyramid_routesalchemy`` scaffold function of the same name; it didn't get synchronized when it was changed in the scaffold. --- CHANGES.txt | 11 +++++++++++ TODO.txt | 7 ------- docs/tutorials/wiki2/src/authorization/tutorial/models.py | 2 +- docs/tutorials/wiki2/src/basiclayout/tutorial/models.py | 2 +- docs/tutorials/wiki2/src/models/tutorial/models.py | 2 +- docs/tutorials/wiki2/src/tests/tutorial/models.py | 2 +- docs/tutorials/wiki2/src/views/tutorial/models.py | 2 +- pyramid/scaffolds/alchemy/+package+/models.py | 2 +- pyramid/scaffolds/routesalchemy/+package+/models.py | 2 +- 9 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 403969fc0..fd11a16d1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,17 @@ Bug Fixes second route with the name of a previously added name or c) use a Configurator that works in ``autocommit`` mode. +- The ``pyramid_routesalchemy`` and ``pyramid_alchemy`` scaffolds + inappropriately used ``DBSession.rollback()`` instead of + ``transaction.abort()`` in one place. + +Documentation +------------- + +- Wiki2 (SQLAlchemy + URL Dispatch) tutorial ``models.initialize_sql`` didn't + match the ``pyramid_routesalchemy`` scaffold function of the same name; it + didn't get synchronized when it was changed in the scaffold. + 1.1b1 (2011-07-10) ================== diff --git a/TODO.txt b/TODO.txt index 85b11ff06..06eb4d143 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,13 +1,6 @@ Pyramid TODOs ============= -Must-Have ---------- - -- tutorial models.initialize_sql doesn't match scaffold - (DBSession.rollback()/transaction.abort() in scaffold vs. "pass" in - tutorial) - Should-Have ----------- diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py index 53c6d1122..832545cb1 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -41,7 +41,7 @@ def initialize_sql(engine): transaction.commit() except IntegrityError: # already created - pass + transaction.abort() class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py index 4fd010c5c..9b687931b 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py @@ -40,4 +40,4 @@ def initialize_sql(engine): try: populate() except IntegrityError: - pass + transaction.abort() diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index ecc8d567b..30f77a0b9 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -39,4 +39,4 @@ def initialize_sql(engine): transaction.commit() except IntegrityError: # already created - pass + transaction.abort() diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models.py index 53c6d1122..832545cb1 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models.py @@ -41,7 +41,7 @@ def initialize_sql(engine): transaction.commit() except IntegrityError: # already created - pass + transaction.abort() class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py index 960c14941..30506f67e 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -38,4 +38,4 @@ def initialize_sql(engine): transaction.commit() except IntegrityError: # already created - pass + transaction.abort() diff --git a/pyramid/scaffolds/alchemy/+package+/models.py b/pyramid/scaffolds/alchemy/+package+/models.py index f1b47f98c..4a93ecf8a 100755 --- a/pyramid/scaffolds/alchemy/+package+/models.py +++ b/pyramid/scaffolds/alchemy/+package+/models.py @@ -76,7 +76,7 @@ def initialize_sql(engine): try: populate() except IntegrityError: - DBSession.rollback() + transaction.abort() return DBSession def appmaker(engine): diff --git a/pyramid/scaffolds/routesalchemy/+package+/models.py b/pyramid/scaffolds/routesalchemy/+package+/models.py index a036e8c91..e942732ec 100644 --- a/pyramid/scaffolds/routesalchemy/+package+/models.py +++ b/pyramid/scaffolds/routesalchemy/+package+/models.py @@ -39,4 +39,4 @@ def initialize_sql(engine): try: populate() except IntegrityError: - DBSession.rollback() + transaction.abort() -- cgit v1.2.3 From e573d4356ed0371f5ba34ff3ff396fefd2e55913 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 20:56:53 -0400 Subject: - New environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and new configuration file value ``prevent_http_cache``. These are synomymous and allow you to prevent HTTP cache headers from being set by Pyramid's ``http_cache`` machinery globally in a process. see the "Influencing HTTP Caching" section of the "View Configuration" narrative chapter and the detailed documentation for this setting in the "Environment Variables and Configuration Settings" narrative chapter. - New documentation section in View Configuration narrative chapter: "Influencing HTTP Caching". --- CHANGES.txt | 14 ++++++++++++ docs/narr/environment.rst | 19 ++++++++++++++++ docs/narr/viewconfig.rst | 50 ++++++++++++++++++++++++++++++++++++++++++ docs/whatsnew-1.1.rst | 6 +++++ pyramid/config.py | 8 +++++-- pyramid/settings.py | 7 +++++- pyramid/tests/test_config.py | 15 +++++++++++++ pyramid/tests/test_settings.py | 15 +++++++++++++ 8 files changed, 131 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fd11a16d1..ea83827cc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,17 @@ Next Release ============ +Features +-------- + +- New environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and new + configuration file value ``prevent_http_cache``. These are synomymous and + allow you to prevent HTTP cache headers from being set by Pyramid's + ``http_cache`` machinery globally in a process. see the "Influencing HTTP + Caching" section of the "View Configuration" narrative chapter and the + detailed documentation for this setting in the "Environment Variables and + Configuration Settings" narrative chapter. + Behavior Changes ---------------- @@ -41,6 +52,9 @@ Documentation match the ``pyramid_routesalchemy`` scaffold function of the same name; it didn't get synchronized when it was changed in the scaffold. +- New documentation section in View Configuration narrative chapter: + "Influencing HTTP Caching". + 1.1b1 (2011-07-10) ================== diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index a57b316e1..7f8e3953d 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -117,6 +117,25 @@ this value is true. See also :ref:`debug_routematch_section`. | | | +---------------------------------+-----------------------------+ +.. _preventing_http_caching: + +Preventing HTTP Caching +------------------------ + +Prevent the ``http_cache`` view configuration argument from having any effect +globally in this process when this value is true. No http caching-related +response headers will be set by the Pyramid ``http_cache`` view configuration +feature when this is true. See also :ref:`influencing_http_caching`. + ++---------------------------------+-----------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+=============================+ +| ``PYRAMID_PREVENT_HTTP_CACHE`` | ``prevent_http_cache`` | +| | | +| | | +| | | ++---------------------------------+-----------------------------+ + Debugging All ------------- diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 326d97506..1ef40e89f 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -82,6 +82,8 @@ being invoked due to an authorization policy. The presence of non-predicate arguments in a view configuration does not narrow the circumstances in which the view callable will be invoked. +.. _nonpredicate_view_args: + Non-Predicate Arguments +++++++++++++++++++++++ @@ -767,6 +769,54 @@ found will be printed to ``stderr``, and the browser representation of the error will include the same information. See :ref:`environment_chapter` for more information about how, and where to set these values. +.. index:: + single: HTTP caching + +.. _influencing_http_caching: + +Influencing HTTP Caching +------------------------ + +.. note:: This feature is new in Pyramid 1.1. + +When a non-``None`` ``http_cache`` argument is passed to a view +configuration, Pyramid will set ``Expires`` and ``Cache-Control`` response +headers in the resulting response, causing browsers to cache the response +data for some time. See ``http_cache`` in :ref:`nonpredicate_view_args` for +the its allowable values and what they mean. + +Sometimes it's undesirable to have these headers set as the result of +returning a response from a view, even though you'd like to decorate the view +with a view configuration decorator that has ``http_cache``. Perhaps there's +an alternate branch in your view code that returns a response that should +never be cacheable, while the "normal" branch returns something that should +always be cacheable. If this is the case, set the ``prevent_auto`` attribute +of the ``response.cache_control`` object to a non-``False`` value. For +example, the below view callable is configured with a ``@view_config`` +decorator that indicates any response from the view should be cached for 3600 +seconds. However, the view itself prevents caching from taking place unless +there's a ``should_cache`` GET or POST variable: + +.. code-block:: python + + from pyramid.view import view_config + + @view_config(http_cache=3600) + def view(request): + response = Response() + if not 'should_cache' in request.params: + response.cache_control.prevent_auto = True + return response + +Note that the ``http_cache`` machinery will overwrite or add to caching +headers you set within the view itself unless you use ``preserve_auto``. + +You can also turn of the effect of ``http_cache`` entirely for the duration +of a Pyramid application lifetime. To do so, set the +``PYRAMID_PREVENT_HTTP_CACHE`` environment variable or the +``prevent_http_cache`` configuration value setting to a true value. For more +information, see :ref:`preventing_http_caching`. + .. index:: pair: matching views; printing single: paster pviews diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index b55b30238..20b346090 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -167,6 +167,12 @@ Minor Feature Additions to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. + The environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and configuration + file value ``prevent_http_cache`` are synomymous and allow you to prevent + HTTP cache headers from being set by Pyramid's ``http_cache`` machinery + globally in a process. see :ref:`influencing_http_caching` and + :ref:`preventing_http_caching`. + - A `JSONP `_ renderer. See :ref:`jsonp_renderer` for more details. diff --git a/pyramid/config.py b/pyramid/config.py index 6efb6c00e..44ce5110e 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2970,6 +2970,9 @@ class ViewDeriver(object): @wraps_view def http_cached_view(self, view): + if self.registry.settings.get('prevent_http_cache', False): + return view + seconds = self.kw.get('http_cache') if seconds is None: @@ -2987,8 +2990,9 @@ class ViewDeriver(object): def wrapper(context, request): response = view(context, request) - cache_control = response.cache_control - if not hasattr(cache_control, 'prevent_auto'): + prevent_caching = getattr(response.cache_control, 'prevent_auto', + False) + if not prevent_caching: response.cache_expires(seconds, **options) return response diff --git a/pyramid/settings.py b/pyramid/settings.py index edea9ce99..7ba53ea4c 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -48,7 +48,11 @@ class Settings(dict): eff_reload_assets = reload_assets or reload_resources locale_name = self.get('default_locale_name', 'en') eff_locale_name = eget('PYRAMID_DEFAULT_LOCALE_NAME', locale_name) - + + config_prevent_http_cache = self.get('prevent_http_cache', '') + eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE', + config_prevent_http_cache)) + update = { 'debug_authorization': eff_debug_all or eff_debug_auth, 'debug_notfound': eff_debug_all or eff_debug_notfound, @@ -58,6 +62,7 @@ class Settings(dict): 'reload_resources':eff_reload_all or eff_reload_assets, 'reload_assets':eff_reload_all or eff_reload_assets, 'default_locale_name':eff_locale_name, + 'prevent_http_cache':eff_prevent_http_cache, } self.update(update) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 84848c2ad..002eab8e8 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -4340,6 +4340,21 @@ class TestViewDeriver(unittest.TestCase): self.assertFalse('Expires' in headers) self.assertFalse('Cache-Control' in headers) + def test_http_cached_prevent_http_cache_in_settings(self): + self.config.registry.settings['prevent_http_cache'] = True + from pyramid.response import Response + response = Response() + def inner_view(context, request): + return response + deriver = self._makeOne(http_cache=3600) + result = deriver(inner_view) + request = self._makeRequest() + result = result(None, request) + self.assertEqual(result, response) + headers = dict(result.headerlist) + self.assertFalse('Expires' in headers) + self.assertFalse('Cache-Control' in headers) + def test_http_cached_view_bad_tuple(self): from pyramid.exceptions import ConfigurationError deriver = self._makeOne(http_cache=(None,)) diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py index a444539e0..75d653133 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -28,6 +28,21 @@ class TestSettings(unittest.TestCase): self.assertEqual(settings['reload_templates'], False) self.assertEqual(settings['reload_resources'], False) + def test_prevent_http_cache(self): + settings = self._makeOne({}) + self.assertEqual(settings['prevent_http_cache'], False) + result = self._makeOne({'prevent_http_cache':'false'}) + self.assertEqual(result['prevent_http_cache'], False) + result = self._makeOne({'prevent_http_cache':'t'}) + self.assertEqual(result['prevent_http_cache'], True) + result = self._makeOne({'prevent_http_cache':'1'}) + self.assertEqual(result['prevent_http_cache'], True) + result = self._makeOne({}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) + self.assertEqual(result['prevent_http_cache'], True) + result = self._makeOne({'prevent_http_cache':'false'}, + {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) + self.assertEqual(result['prevent_http_cache'], True) + def test_reload_templates(self): settings = self._makeOne({}) self.assertEqual(settings['reload_templates'], False) -- cgit v1.2.3 From 5fa17fdefe848d8004ca9a6daab19e1e1ea17b29 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 20:59:11 -0400 Subject: clarify --- docs/narr/viewconfig.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 1ef40e89f..d33d78752 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -817,6 +817,11 @@ of a Pyramid application lifetime. To do so, set the ``prevent_http_cache`` configuration value setting to a true value. For more information, see :ref:`preventing_http_caching`. +Note that setting ``prevent_http_cache`` will have no effect on caching +headers that your application code itself sets. It will only prevent caching +headers that would have been set by the Pyramid HTTP caching machinery +invoked as the result of the ``http_cache`` argument to view configuration. + .. index:: pair: matching views; printing single: paster pviews -- cgit v1.2.3 From 3d3abc4b253d9248eb973d804ba6f4a0090e5047 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 21:02:02 -0400 Subject: garden --- TODO.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.txt b/TODO.txt index 06eb4d143..56fd89584 100644 --- a/TODO.txt +++ b/TODO.txt @@ -15,6 +15,8 @@ Should-Have Nice-to-Have ------------ +- Rename all config file values with a ``pyramid.`` prefix. + - Maybe add ``add_renderer_globals`` method to Configurator. - Speed up startup time (defer _bootstrap and registerCommonDirectives() -- cgit v1.2.3 From 60bc3075c005984969f60eb3b768ce7214135514 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 21:06:29 -0400 Subject: garden --- TODO.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO.txt b/TODO.txt index 56fd89584..84bd14153 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,12 @@ Pyramid TODOs ============= +Must-Have +--------- + +- Investigate whether we should replace request.response before we invoke an + exception view. + Should-Have ----------- -- cgit v1.2.3 From d05117e9655e3619e66bfef86f40043fbcc61829 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 13 Jul 2011 00:41:59 -0400 Subject: - We now clear ``request.response`` before we invoke an exception view; an exception view will be working with a request.response that has not been touched by any code prior to the exception. --- CHANGES.txt | 4 ++++ TODO.txt | 6 ------ pyramid/router.py | 6 ++++++ pyramid/tests/test_router.py | 27 +++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ea83827cc..21bea6572 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -45,6 +45,10 @@ Bug Fixes inappropriately used ``DBSession.rollback()`` instead of ``transaction.abort()`` in one place. +- We now clear ``request.response`` before we invoke an exception view; an + exception view will be working with a request.response that has not been + touched by any code prior to the exception. + Documentation ------------- diff --git a/TODO.txt b/TODO.txt index 84bd14153..56fd89584 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,12 +1,6 @@ Pyramid TODOs ============= -Must-Have ---------- - -- Investigate whether we should replace request.response before we invoke an - exception view. - Should-Have ----------- diff --git a/pyramid/router.py b/pyramid/router.py index 880429424..0a92b5cd5 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -162,6 +162,12 @@ class Router(object): # handle exceptions raised during root finding and view-exec except Exception, why: + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] + attrs['exception'] = why for_ = (IExceptionViewClassifier, diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index af311cfc2..6cd86901e 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -403,6 +403,33 @@ class TestRouter(unittest.TestCase): why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertEqual(why[0], 'notfound') + def test_call_view_raises_response_cleared(self): + from zope.interface import Interface + from zope.interface import directlyProvides + from pyramid.interfaces import IExceptionViewClassifier + class IContext(Interface): + pass + from pyramid.interfaces import IRequest + from pyramid.interfaces import IViewClassifier + context = DummyContext() + directlyProvides(context, IContext) + self._registerTraverserFactory(context, subpath=['']) + def view(context, request): + request.response.a = 1 + raise KeyError + def exc_view(context, request): + self.failIf(hasattr(request.response, 'a')) + request.response.body = 'OK' + return request.response + environ = self._makeEnviron() + self._registerView(view, '', IViewClassifier, IRequest, IContext) + self._registerView(exc_view, '', IExceptionViewClassifier, + IRequest, KeyError) + router = self._makeOne() + start_response = DummyStartResponse() + itera = router(environ, start_response) + self.assertEqual(itera, ['OK']) + def test_call_request_has_response_callbacks(self): from zope.interface import Interface from zope.interface import directlyProvides -- cgit v1.2.3 From 873d9be3a787793caeb66223d2bb68dbc1fc396e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 13 Jul 2011 01:54:03 -0400 Subject: - Views associated with routes with spaces in the route name may not have been looked up correctly when using Pyramid with ``zope.interface`` 3.6.4 and better. Closes #232. --- CHANGES.txt | 4 ++++ pyramid/request.py | 15 ++++++++++++--- pyramid/tests/test_request.py | 9 +++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 21bea6572..71491cb9b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -49,6 +49,10 @@ Bug Fixes exception view will be working with a request.response that has not been touched by any code prior to the exception. +- Views associated with routes with spaces in the route name may not have + been looked up correctly when using Pyramid with ``zope.interface`` 3.6.4 + and better. + Documentation ------------- diff --git a/pyramid/request.py b/pyramid/request.py index 1bf044b69..8df204681 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -496,10 +496,19 @@ class Request(BaseRequest, DeprecatedRequestMethods): def route_request_iface(name, bases=()): - iface = InterfaceClass('%s_IRequest' % name, bases=bases) + # zope.interface treats the __name__ as the __doc__ and changes __name__ + # to None for interfaces that contain spaces if you do not pass a + # nonempty __doc__ (insane); see + # zope.interface.interface.Element.__init__ and + # https://github.com/Pylons/pyramid/issues/232; as a result, always pass + # __doc__ to the InterfaceClass constructor. + iface = InterfaceClass('%s_IRequest' % name, bases=bases, + __doc__="route_request_iface-generated interface") # for exception view lookups - iface.combined = InterfaceClass('%s_combined_IRequest' % name, - bases=(iface, IRequest)) + iface.combined = InterfaceClass( + '%s_combined_IRequest' % name, + bases=(iface, IRequest), + __doc__ = 'route_request_iface-generated combined interface') return iface def add_global_response_headers(request, headerlist): diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 74bc25359..066aa9207 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -424,6 +424,15 @@ class Test_route_request_iface(unittest.TestCase): self.assertTrue(hasattr(iface, 'combined')) self.assertEqual(iface.combined.__name__, 'routename_combined_IRequest') + def test_it_routename_with_spaces(self): + # see https://github.com/Pylons/pyramid/issues/232 + iface = self._callFUT('routename with spaces') + self.assertEqual(iface.__name__, 'routename with spaces_IRequest') + self.assertTrue(hasattr(iface, 'combined')) + self.assertEqual(iface.combined.__name__, + 'routename with spaces_combined_IRequest') + + class Test_add_global_response_headers(unittest.TestCase): def _callFUT(self, request, headerlist): from pyramid.request import add_global_response_headers -- cgit v1.2.3 From d4ccb85f76f38400eaf90b40ccbc56b017e3a6bd Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 13 Jul 2011 03:34:05 -0400 Subject: prep for 1.1b2 --- CHANGES.txt | 6 +++--- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 71491cb9b..c2c0cc86a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next Release -============ +1.1b2 (2011-07-13) +================== Features -------- @@ -51,7 +51,7 @@ Bug Fixes - Views associated with routes with spaces in the route name may not have been looked up correctly when using Pyramid with ``zope.interface`` 3.6.4 - and better. + and better. See https://github.com/Pylons/pyramid/issues/232. Documentation ------------- diff --git a/docs/conf.py b/docs/conf.py index 31d816aff..f0cf5fd53 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.1b1' +version = '1.1b2' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index 54e45e14a..55be461e4 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1b1', + version='1.1b2', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From 2ad827b4c67111f76e9c2fb54d082a50eb2b4c23 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 13 Jul 2011 18:31:41 -0400 Subject: - Fix corner case to ease semifunctional testing of views: create a new rendererinfo to clear out old registry on a rescan. See https://github.com/Pylons/pyramid/pull/234. Closes #234. --- CHANGES.txt | 10 ++++++++++ pyramid/tests/test_view.py | 29 +++++++++++++++++++++++++++-- pyramid/view.py | 7 +++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c2c0cc86a..cd9f42dd9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,13 @@ +Next release +============ + +Features +-------- + +- Fix corner case to ease semifunctional testing of views: create a new + rendererinfo to clear out old registry on a rescan. See + https://github.com/Pylons/pyramid/pull/234. + 1.1b2 (2011-07-13) ================== diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 6bb3b01a0..d46cfb3f5 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -395,6 +395,30 @@ class TestViewConfigDecorator(unittest.TestCase): self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['renderer'], {'a':1}) + def test_call_with_renderer_IRendererInfo(self): + # see https://github.com/Pylons/pyramid/pull/234 + from pyramid.interfaces import IRendererInfo + import pyramid.tests + class DummyRendererHelper(object): + implements(IRendererInfo) + name = 'fixtures/minimal.pt' + package = pyramid.tests + renderer_helper = DummyRendererHelper() + decorator = self._makeOne(renderer=renderer_helper) + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + wrapped = decorator(foo) + self.assertTrue(wrapped is foo) + context = DummyVenusianContext() + settings = call_venusian(venusian, context) + self.assertEqual(len(settings), 1) + renderer = settings[0]['renderer'] + self.assertFalse(renderer is renderer_helper) + self.assertEqual(renderer.name, 'fixtures/minimal.pt') + self.assertEqual(renderer.package, pyramid.tests) + self.assertEqual(renderer.registry, context.config.registry) + class Test_append_slash_notfound_view(BaseTest, unittest.TestCase): def _callFUT(self, context, request): from pyramid.view import append_slash_notfound_view @@ -607,8 +631,9 @@ class DummyVenusianContext(object): def __init__(self): self.config = DummyConfig() -def call_venusian(venusian): - context = DummyVenusianContext() +def call_venusian(venusian, context=None): + if context is None: + context = DummyVenusianContext() for wrapped, callback, category in venusian.attachments: callback(context, None, None) return context.config.settings diff --git a/pyramid/view.py b/pyramid/view.py index ef7352301..6b28601e2 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -7,6 +7,7 @@ from zope.deprecation import deprecated from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier +from pyramid.interfaces import IRendererInfo from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import default_exceptionresponse_view @@ -205,6 +206,12 @@ class view_config(object): renderer = RendererHelper(name=renderer, package=info.module, registry=context.config.registry) + elif IRendererInfo.providedBy(renderer): + # create a new rendererinfo to clear out old registry on a + # rescan, see https://github.com/Pylons/pyramid/pull/234 + renderer = RendererHelper(name=renderer.name, + package=info.module, + registry=context.config.registry) settings['renderer'] = renderer context.config.add_view(view=ob, **settings) -- cgit v1.2.3 From ae4c577d12a16396b45515e81415b2b16f8e93e8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 13 Jul 2011 20:48:38 -0400 Subject: move all paster commands to a separate chapter --- docs/index.rst | 1 + docs/narr/commandline.rst | 288 ++++++++++++++++++++++++++++++++++++++++++++++ docs/narr/project.rst | 148 ++---------------------- docs/narr/urldispatch.rst | 43 +------ docs/narr/viewconfig.rst | 103 +---------------- 5 files changed, 309 insertions(+), 274 deletions(-) create mode 100644 docs/narr/commandline.rst diff --git a/docs/index.rst b/docs/index.rst index a4743af9b..2beec2494 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,7 @@ Narrative documentation in chapter form explaining how to use narr/vhosting narr/events narr/environment + narr/commandline narr/testing narr/hooks narr/advconfig diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst new file mode 100644 index 000000000..0c591f6d1 --- /dev/null +++ b/docs/narr/commandline.rst @@ -0,0 +1,288 @@ +.. _command_line_chapter: + +Command-Line Pyramid +==================== + +Your :app:`Pyramid` application can be controlled and inspected using a +variety of command-line utilities. These utilities are documented in this +chapter. + +.. index:: + pair: matching views; printing + single: paster pviews + +.. _displaying_matching_views: + +Displaying Matching Views for a Given URL +----------------------------------------- + +For a big application with several views, it can be hard to keep the view +configuration details in your head, even if you defined all the views +yourself. You can use the ``paster pviews`` command in a terminal window to +print a summary of matching routes and views for a given URL in your +application. The ``paster pviews`` command accepts two arguments. The +first argument to ``pviews`` is the path to your application's ``.ini`` file +and section name inside the ``.ini`` file which points to your application. +This should be of the format ``config_file#section_name``. The second argument +is the URL to test for matching views. + +Here is an example for a simple view configuration using :term:`traversal`: + +.. code-block:: text + :linenos: + + $ ../bin/paster pviews development.ini tutorial /FrontPage + + URL = /FrontPage + + context: + view name: + + View: + ----- + tutorial.views.view_page + required permission = view + +The output always has the requested URL at the top and below that all the +views that matched with their view configuration details. In this example +only one view matches, so there is just a single *View* section. For each +matching view, the full code path to the associated view callable is shown, +along with any permissions and predicates that are part of that view +configuration. + +A more complex configuration might generate something like this: + +.. code-block:: text + :linenos: + + $ ../bin/paster pviews development.ini#shootout /about + + URL = /about + + context: + view name: about + + Route: + ------ + route name: about + route pattern: /about + route path: /about + subpath: + route predicates (request method = GET) + + View: + ----- + shootout.views.about_view + required permission = view + view predicates (request_param testing, header X/header) + + Route: + ------ + route name: about_post + route pattern: /about + route path: /about + subpath: + route predicates (request method = POST) + + View: + ----- + shootout.views.about_view_post + required permission = view + view predicates (request_param test) + + View: + ----- + shootout.views.about_view_post2 + required permission = view + view predicates (request_param test2) + +In this case, we are dealing with a :term:`URL dispatch` application. This +specific URL has two matching routes. The matching route information is +displayed first, followed by any views that are associated with that route. +As you can see from the second matching route output, a route can be +associated with more than one view. + +For a URL that doesn't match any views, ``paster pviews`` will simply print +out a *Not found* message. + + +.. index:: + single: interactive shell + single: IPython + single: paster pshell + single: pshell + +.. _interactive_shell: + +The Interactive Shell +--------------------- + +Once you've installed your program for development using ``setup.py +develop``, you can use an interactive Python shell to execute expressions in +a Python environment exactly like the one that will be used when your +application runs "for real". To do so, use the ``paster pshell`` command. + +The argument to ``pshell`` follows the format ``config_file#section_name`` +where ``config_file`` is the path to your application's ``.ini`` file and +``section_name`` is the ``app`` section name inside the ``.ini`` file which +points to *your application* as opposed to any other section within the +``.ini`` file. For example, if your application ``.ini`` file might have a +``[app:MyProject]`` section that looks like so: + +.. code-block:: ini + :linenos: + + [app:MyProject] + use = egg:MyProject + reload_templates = true + debug_authorization = false + debug_notfound = false + debug_templates = true + default_locale_name = en + +If so, you can use the following command to invoke a debug shell using the +name ``MyProject`` as a section name: + +.. code-block:: text + + [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini#MyProject + Python 2.4.5 (#1, Aug 29 2008, 12:27:37) + [GCC 4.0.1 (Apple Inc. build 5465)] on darwin + + Default Variables: + app The WSGI Application + root The root of the default resource tree. + registry The Pyramid registry object. + settings The Pyramid settings object. + + >>> root + + >>> registry + + >>> settings['debug_notfound'] + False + >>> from myproject.views import my_view + >>> from pyramid.request import Request + >>> r = Request.blank('/') + >>> my_view(r) + {'project': 'myproject'} + +The WSGI application that is loaded will be available in the shell as the +``app`` global. Also, if the application that is loaded is the +:app:`Pyramid` app with no surrounding middleware, the ``root`` object +returned by the default :term:`root factory`, ``registry``, and ``settings`` +will be available. + +The interactive shell will not be able to load some of the globals like +``root``, ``registry`` and ``settings`` if the section name specified when +loading ``pshell`` is not referencing your :app:`Pyramid` application directly. +For example, if you have the following ``.ini`` file content: + +.. code-block:: ini + :linenos: + + [app:MyProject] + use = egg:MyProject + reload_templates = true + debug_authorization = false + debug_notfound = false + debug_templates = true + default_locale_name = en + + [pipeline:main] + pipeline = + egg:WebError#evalerror + MyProject + +Use ``MyProject`` instead of ``main`` as the section name argument to +``pshell`` against the above ``.ini`` file (e.g. ``paster pshell +development.ini#MyProject``). + +Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). + +.. _extending_pshell: + +Extending the Shell +~~~~~~~~~~~~~~~~~~~ + +It is sometimes convenient when using the interactive shell often to have +some variables significant to your application already loaded as globals +when you start the ``pshell``. To facilitate this, ``pshell`` will look +for a special ``[pshell]`` section in your INI file and expose the subsequent +key/value pairs to the shell. + +For example, you want to expose your model to the shell, along with the +database session so that you can mutate the model on an actual database. +Here, we'll assume your model is stored in the ``myapp.models`` package. + +.. code-block:: ini + :linenos: + + [pshell] + m = myapp.models + session = myapp.models.DBSession + t = transaction + +When this INI file is loaded, the extra variables ``m``, ``session`` and +``t`` will be available for use immediately. This happens regardless of +whether the ``registry`` and other special variables are loaded. + +IPython +~~~~~~~ + +If you have `IPython `_ installed in +the interpreter you use to invoke the ``paster`` command, the ``pshell`` +command will use an IPython interactive shell instead of a standard Python +interpreter shell. If you don't want this to happen, even if you have +IPython installed, you can pass the ``--disable-ipython`` flag to the +``pshell`` command to use a standard Python interpreter shell +unconditionally. + +.. code-block:: text + + [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ + development.ini#MyProject + + +.. index:: + pair: routes; printing + single: paster proutes + single: proutes + +.. _displaying_application_routes: + +Displaying All Application Routes +--------------------------------- + +You can use the ``paster proutes`` command in a terminal window to print a +summary of routes related to your application. Much like the ``paster +pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` +command accepts one argument with the format ``config_file#section_name``. +The ``config_file`` is the path to your application's ``.ini`` file, +and ``section_name`` is the ``app`` section name inside the ``.ini`` file +which points to your application. + +For example: + +.. code-block:: text + :linenos: + + [chrism@thinko MyProject]$ ../bin/paster proutes development.ini#MyProject + Name Pattern View + ---- ------- ---- + home / + home2 / + another /another None + static/ static/*subpath + catchall /*subpath + +``paster proutes`` generates a table. The table has three columns: a Name +name column, a Pattern column, and a View column. The items listed in the +Name column are route names, the items listen in the Pattern column are route +patterns, and the items listed in the View column are representations of the +view callable that will be invoked when a request matches the associated +route pattern. The view column may show ``None`` if no associated view +callable could be found. If no routes are configured within your +application, nothing will be printed to the console when ``paster proutes`` +is executed. + diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 4a7f63176..4b08d09f6 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -194,7 +194,8 @@ Elided output from a run of this command is shown below: This will install a :term:`distribution` representing your project into the interpreter's library set so it can be found by ``import`` statements and by -:term:`PasteDeploy` commands such as ``paster serve`` and ``paster pshell``. +:term:`PasteDeploy` commands such as ``paster serve``, ``paster pshell``, +``paster proutes`` and ``paster pviews``. .. index:: single: running tests @@ -243,142 +244,6 @@ The tests themselves are found in the ``tests.py`` module in your ``paster create`` -generated project. Within a project generated by the ``pyramid_starter`` scaffold, a single sample test exists. -.. index:: - single: interactive shell - single: IPython - single: paster pshell - -.. _interactive_shell: - -The Interactive Shell ---------------------- - -Once you've installed your program for development using ``setup.py -develop``, you can use an interactive Python shell to examine your -:app:`Pyramid` project's :term:`resource` and :term:`view` objects from a -Python prompt. To do so, use your virtualenv's ``paster pshell`` command. - -The argument to ``pshell`` follows the format ``config_file#section_name`` -where ``config_file`` is the path to your application's ``.ini`` file and -``section_name`` is the ``app`` section name inside the ``.ini`` file which -points to *your application* as opposed to any other section within the -``.ini`` file. For example, if your application ``.ini`` file might have a -``[app:MyProject]`` section that looks like so: - -.. code-block:: ini - :linenos: - - [app:MyProject] - use = egg:MyProject - reload_templates = true - debug_authorization = false - debug_notfound = false - debug_templates = true - default_locale_name = en - -If so, you can use the following command to invoke a debug shell using the -name ``MyProject`` as a section name: - -.. code-block:: text - - [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini#MyProject - Python 2.4.5 (#1, Aug 29 2008, 12:27:37) - [GCC 4.0.1 (Apple Inc. build 5465)] on darwin - - Default Variables: - app The WSGI Application - root The root of the default resource tree. - registry The Pyramid registry object. - settings The Pyramid settings object. - - >>> root - - >>> registry - - >>> settings['debug_notfound'] - False - >>> from myproject.views import my_view - >>> from pyramid.request import Request - >>> r = Request.blank('/') - >>> my_view(r) - {'project': 'myproject'} - -The WSGI application that is loaded will be available in the shell as the -``app`` global. Also, if the application that is loaded is the -:app:`Pyramid` app with no surrounding middleware, the ``root`` object -returned by the default :term:`root factory`, ``registry``, and ``settings`` -will be available. - -The interactive shell will not be able to load some of the globals like -``root``, ``registry`` and ``settings`` if the section name specified when -loading ``pshell`` is not referencing your :app:`Pyramid` application directly. -For example, if you have the following ``.ini`` file content: - -.. code-block:: ini - :linenos: - - [app:MyProject] - use = egg:MyProject - reload_templates = true - debug_authorization = false - debug_notfound = false - debug_templates = true - default_locale_name = en - - [pipeline:main] - pipeline = - egg:WebError#evalerror - MyProject - -Use ``MyProject`` instead of ``main`` as the section name argument to -``pshell`` against the above ``.ini`` file (e.g. ``paster pshell -development.ini#MyProject``). - -Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). - -.. _extending_pshell: - -Extending the Shell -~~~~~~~~~~~~~~~~~~~ - -It is sometimes convenient when using the interactive shell often to have -some variables significant to your application already loaded as globals -when you start the ``pshell``. To facilitate this, ``pshell`` will look -for a special ``[pshell]`` section in your INI file and expose the subsequent -key/value pairs to the shell. - -For example, you want to expose your model to the shell, along with the -database session so that you can mutate the model on an actual database. -Here, we'll assume your model is stored in the ``myapp.models`` package. - -.. code-block:: ini - :linenos: - - [pshell] - m = myapp.models - session = myapp.models.DBSession - t = transaction - -When this INI file is loaded, the extra variables ``m``, ``session`` and -``t`` will be available for use immediately. This happens regardless of -whether the ``registry`` and other special variables are loaded. - -IPython -~~~~~~~ - -If you have `IPython `_ installed in -the interpreter you use to invoke the ``paster`` command, the ``pshell`` -command will use an IPython interactive shell instead of a standard Python -interpreter shell. If you don't want this to happen, even if you have -IPython installed, you can pass the ``--disable-ipython`` flag to the -``pshell`` command to use a standard Python interpreter shell -unconditionally. - -.. code-block:: text - - [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ - development.ini#MyProject - .. index:: single: running an application single: paster serve @@ -1067,4 +932,13 @@ This pattern can be used to rearrage code referred to by any Pyramid API argument which accepts a :term:`dotted Python name` or direct object reference. +Using the Interactive Shell +--------------------------- + +It is possible to use a Python interpreter prompt loaded with a similar +configuration as would be loaded if you were running your Pyramid application +via ``paster serve``. This can be a useful debugging tool. See +:ref:`interactive_shell` for more details. + + diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 52311682e..b0a7009e3 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1028,46 +1028,9 @@ which you started the application from. For example: See :ref:`environment_chapter` for more information about how, and where to set these values. -.. index:: - pair: routes; printing - single: paster proutes - -.. _displaying_application_routes: - -Displaying All Application Routes ---------------------------------- - -You can use the ``paster proutes`` command in a terminal window to print a -summary of routes related to your application. Much like the ``paster -pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` -command accepts one argument with the format ``config_file#section_name``. -The ``config_file`` is the path to your application's ``.ini`` file, -and ``section_name`` is the ``app`` section name inside the ``.ini`` file -which points to your application. - -For example: - -.. code-block:: text - :linenos: - - [chrism@thinko MyProject]$ ../bin/paster proutes development.ini#MyProject - Name Pattern View - ---- ------- ---- - home / - home2 / - another /another None - static/ static/*subpath - catchall /*subpath - -``paster proutes`` generates a table. The table has three columns: a Name -name column, a Pattern column, and a View column. The items listed in the -Name column are route names, the items listen in the Pattern column are route -patterns, and the items listed in the View column are representations of the -view callable that will be invoked when a request matches the associated -route pattern. The view column may show ``None`` if no associated view -callable could be found. If no routes are configured within your -application, nothing will be printed to the console when ``paster proutes`` -is executed. +You can also use the ``paster proutes`` command to see a display of all the +routes configured in your application; for more information, see +:ref:`displaying_application_routes`. Route View Callable Registration and Lookup Details --------------------------------------------------- diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index d33d78752..a45ebae32 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -822,101 +822,10 @@ headers that your application code itself sets. It will only prevent caching headers that would have been set by the Pyramid HTTP caching machinery invoked as the result of the ``http_cache`` argument to view configuration. -.. index:: - pair: matching views; printing - single: paster pviews - -.. _displaying_matching_views: - -Displaying Matching Views for a Given URL ------------------------------------------ - -For a big application with several views, it can be hard to keep the view -configuration details in your head, even if you defined all the views -yourself. You can use the ``paster pviews`` command in a terminal window to -print a summary of matching routes and views for a given URL in your -application. The ``paster pviews`` command accepts two arguments. The -first argument to ``pviews`` is the path to your application's ``.ini`` file -and section name inside the ``.ini`` file which points to your application. -This should be of the format ``config_file#section_name``. The second argument -is the URL to test for matching views. - -Here is an example for a simple view configuration using :term:`traversal`: - -.. code-block:: text - :linenos: - - $ ../bin/paster pviews development.ini tutorial /FrontPage - - URL = /FrontPage - - context: - view name: - - View: - ----- - tutorial.views.view_page - required permission = view - -The output always has the requested URL at the top and below that all the -views that matched with their view configuration details. In this example -only one view matches, so there is just a single *View* section. For each -matching view, the full code path to the associated view callable is shown, -along with any permissions and predicates that are part of that view -configuration. - -A more complex configuration might generate something like this: - -.. code-block:: text - :linenos: - - $ ../bin/paster pviews development.ini#shootout /about - - URL = /about - - context: - view name: about - - Route: - ------ - route name: about - route pattern: /about - route path: /about - subpath: - route predicates (request method = GET) - - View: - ----- - shootout.views.about_view - required permission = view - view predicates (request_param testing, header X/header) - - Route: - ------ - route name: about_post - route pattern: /about - route path: /about - subpath: - route predicates (request method = POST) - - View: - ----- - shootout.views.about_view_post - required permission = view - view predicates (request_param test) - - View: - ----- - shootout.views.about_view_post2 - required permission = view - view predicates (request_param test2) - -In this case, we are dealing with a :term:`URL dispatch` application. This -specific URL has two matching routes. The matching route information is -displayed first, followed by any views that are associated with that route. -As you can see from the second matching route output, a route can be -associated with more than one view. - -For a URL that doesn't match any views, ``paster pviews`` will simply print -out a *Not found* message. +Debugging View Configuration +---------------------------- +See :ref:`displaying_matching_views` for information about how to display +each of the view callables that might match for a given URL. This can be an +effective way to figure out why a particular view callable is being called +instead of the one you'd like to be called. -- cgit v1.2.3 From 56d0fe4a9f97daa4d5fd0c28ea83c6ef32856b3d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 01:13:27 -0400 Subject: - New API class: ``pyramid.static.static_view``. This supersedes the deprecated ``pyramid.view.static`` class. ``pyramid.satic.static_view`` by default serves up documents as the result of the request's ``path_info``, attribute rather than it's ``subpath`` attribute (the inverse was true of ``pyramid.view.static``, and still is). ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when you don't want the static view to behave like the older deprecated version. - The ``pyramid.view.static`` class has been deprecated in favor of the newer ``pyramid.static.static_view`` class. A deprecation warning is raised when it is used. You should replace it with a reference to ``pyramid.static.static_view`` with the ``use_subpath=True`` argument. --- CHANGES.txt | 16 +++++++++++ docs/api.rst | 1 + docs/api/static.rst | 11 ++++++++ docs/narr/assets.rst | 29 ++++++++++---------- docs/narr/hybrid.rst | 17 +++++++----- pyramid/static.py | 18 ++++++++++--- pyramid/tests/test_integration.py | 4 +-- pyramid/tests/test_static.py | 56 ++++++++++++++++++++++++++------------- pyramid/tests/test_view.py | 54 +++++++++++++++++++++++++++++++++++++ pyramid/view.py | 25 ++++++++++++++--- 10 files changed, 184 insertions(+), 47 deletions(-) create mode 100644 docs/api/static.rst diff --git a/CHANGES.txt b/CHANGES.txt index cd9f42dd9..d898c5ca6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,22 @@ Features rendererinfo to clear out old registry on a rescan. See https://github.com/Pylons/pyramid/pull/234. +- New API class: ``pyramid.static.static_view``. This supersedes the + deprecated ``pyramid.view.static`` class. ``pyramid.satic.static_view`` by + default serves up documents as the result of the request's ``path_info``, + attribute rather than it's ``subpath`` attribute (the inverse was true of + ``pyramid.view.static``, and still is). ``pyramid.static.static_view`` + exposes a ``use_subpath`` flag for use when you don't want the static view + to behave like the older deprecated version. + +Deprecations +------------ + +- The ``pyramid.view.static`` class has been deprecated in favor of the newer + ``pyramid.static.static_view`` class. A deprecation warning is raised when + it is used. You should replace it with a reference to + ``pyramid.static.static_view`` with the ``use_subpath=True`` argument. + 1.1b2 (2011-07-13) ================== diff --git a/docs/api.rst b/docs/api.rst index be7942502..a7e1566d3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -28,6 +28,7 @@ documentation is organized alphabetically by module name. api/security api/session api/settings + api/static api/testing api/threadlocal api/traversal diff --git a/docs/api/static.rst b/docs/api/static.rst new file mode 100644 index 000000000..c28473584 --- /dev/null +++ b/docs/api/static.rst @@ -0,0 +1,11 @@ +.. _static_module: + +:mod:`pyramid.static` +--------------------- + +.. automodule:: pyramid.static + + .. autoclass:: static_view + :members: + :inherited-members: + diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 0d50b0106..d57687477 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -299,7 +299,7 @@ URLs against assets made accessible by registering a custom static view. Root-Relative Custom Static View (URL Dispatch Only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The :class:`pyramid.view.static` helper class generates a Pyramid view +The :class:`pyramid.static.static_view` helper class generates a Pyramid view callable. This view callable can serve static assets from a directory. An instance of this class is actually used by the :meth:`~pyramid.config.Configurator.add_static_view` configuration method, so @@ -310,26 +310,27 @@ its behavior is almost exactly the same once it's configured. exclusively. The root-relative route we'll be registering will always be matched before traversal takes place, subverting any views registered via ``add_view`` (at least those without a ``route_name``). A - :class:`~pyramid.view.static` static view cannot be made root-relative when - you use traversal. + :class:`~pyramid.static.static_view` static view cannot be made + root-relative when you use traversal unless it's registered as a + :term:`NotFound view`. To serve files within a directory located on your filesystem at ``/path/to/static/dir`` as the result of a "catchall" route hanging from the root that exists at the end of your routing table, create an instance of the -:class:`~pyramid.view.static` class inside a ``static.py`` file in your -application root as below. +:class:`~pyramid.static.static_view` class inside a ``static.py`` file in +your application root as below. .. ignore-next-block .. code-block:: python :linenos: - from pyramid.view import static - static_view = static('/path/to/static/dir') + from pyramid.static import static + static_view = static_view('/path/to/static/dir', use_subpath=True) .. note:: For better cross-system flexibility, use an :term:`asset - specification` as the argument to :class:`~pyramid.view.static` instead of - a physical absolute filesystem path, e.g. ``mypackage:static`` instead of - ``/path/to/mypackage/static``. + specification` as the argument to :class:`~pyramid.static.static_view` + instead of a physical absolute filesystem path, e.g. ``mypackage:static`` + instead of ``/path/to/mypackage/static``. Subsequently, you may wire the files that are served by this view up to be accessible as ``/`` using a configuration method in your @@ -345,8 +346,8 @@ application's startup code. config.add_view('myapp.static.static_view', route_name='catchall_static') The special name ``*subpath`` above is used by the -:class:`~pyramid.view.static` view callable to signify the path of the file -relative to the directory you're serving. +:class:`~pyramid.static.static_view` view callable to signify the path of the +file relative to the directory you're serving. Registering A View Callable to Serve a "Static" Asset ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -425,10 +426,10 @@ feature, a :term:`Configurator` API exists named - A directory containing multiple Chameleon templates. - Individual static files served up by an instance of the - ``pyramid.view.static`` helper class. + ``pyramid.static.static_view`` helper class. - A directory of static files served up by an instance of the - ``pyramid.view.static`` helper class. + ``pyramid.static.static_view`` helper class. - Any other asset (or set of assets) addressed by code that uses the setuptools :term:`pkg_resources` API. diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 97adaeafd..a0a6a108c 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -431,8 +431,9 @@ Using ``*subpath`` in a Route Pattern There are certain extremely rare cases when you'd like to influence the traversal :term:`subpath` when a route matches without actually performing traversal. For instance, the :func:`pyramid.wsgi.wsgiapp2` decorator and the -:class:`pyramid.view.static` helper attempt to compute ``PATH_INFO`` from the -request's subpath, so it's useful to be able to influence this value. +:class:`pyramid.static.static_view` helper attempt to compute ``PATH_INFO`` +from the request's subpath when its ``use_subpath`` argument is ``True``, so +it's useful to be able to influence this value. When ``*subpath`` exists in a pattern, no path is actually traversed, but the traversal algorithm will return a :term:`subpath` list implied @@ -442,12 +443,16 @@ commonly in route declarations that look like this: .. code-block:: python :linenos: + from pryamid.static import static_view + + www = static_view('mypackage:static', use_subpath=True) + config.add_route('static', '/static/*subpath') - config.add_view('mypackage.views.static_view', route_name='static') + config.add_view(www, route_name='static') -Where ``mypackage.views.static_view`` is an instance of -:class:`pyramid.view.static`. This effectively tells the static helper to -traverse everything in the subpath as a filename. +``mypackage.views.www`` is an instance of +:class:`pyramid.static.static_view`. This effectively tells the static +helper to traverse everything in the subpath as a filename. Corner Cases ------------ diff --git a/pyramid/static.py b/pyramid/static.py index ec7b4cb00..9d8afc09b 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -136,7 +136,8 @@ class StaticURLInfo(object): # it's a view name cache_max_age = extra.pop('cache_max_age', None) # create a view - view = static_view(spec, cache_max_age=cache_max_age) + view = static_view(spec, cache_max_age=cache_max_age, + use_subpath=True) # Mutate extra to allow factory, etc to be passed through here. # Treat permission specially because we'd like to default to @@ -199,6 +200,13 @@ class static_view(object): response headers returned by the view (default is 3600 seconds or five minutes). + ``use_subpath`` influences whether ``request.subpath`` will be used as + ``PATH_INFO`` when calling the underlying WSGI application which actually + serves the static files. If it is ``True``, the static application will + consider ``request.subpath`` as ``PATH_INFO`` input. If it is ``False``, + the static application will consider request.path_info as ``PATH_INFO`` + input. By default, this is ``False``. + .. note:: If the ``root_dir`` is relative to a :term:`package`, or is a :term:`asset specification` the :app:`Pyramid` :class:`pyramid.config.Configurator` method can be @@ -207,7 +215,8 @@ class static_view(object): absolute, configuration will not be able to override the assets it contains. """ - def __init__(self, root_dir, cache_max_age=3600, package_name=None): + def __init__(self, root_dir, cache_max_age=3600, package_name=None, + use_subpath=False): # package_name is for bw compat; it is preferred to pass in a # package-relative path as root_dir # (e.g. ``anotherpackage:foo/static``). @@ -220,6 +229,9 @@ class static_view(object): app = PackageURLParser( package_name, root_dir, cache_max_age=cache_max_age) self.app = app + self.use_subpath = use_subpath def __call__(self, context, request): - return call_app_with_subpath_as_path_info(request, self.app) + if self.use_subpath: + return call_app_with_subpath_as_path_info(request, self.app) + return request.get_response(self.app) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 0ef1e1631..1ebf83062 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -3,7 +3,7 @@ import unittest from pyramid.wsgi import wsgiapp from pyramid.view import view_config -from pyramid.view import static +from pyramid.static import static_view from zope.interface import Interface @@ -42,7 +42,7 @@ class WGSIAppPlusViewConfigTests(unittest.TestCase): self.assertEqual(view.__original_view__, wsgiapptest) here = os.path.dirname(__file__) -staticapp = static(os.path.join(here, 'fixtures')) +staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=True) class TestStaticApp(unittest.TestCase): def test_basic(self): diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index e7506628a..a15459da2 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -190,11 +190,12 @@ class Test_static_view(unittest.TestCase): cleanUp() def _getTargetClass(self): - from pyramid.view import static - return static + from pyramid.static import static_view + return static_view - def _makeOne(self, path, package_name=None): - return self._getTargetClass()(path, package_name=package_name) + def _makeOne(self, path, package_name=None, use_subpath=False): + return self._getTargetClass()(path, package_name=package_name, + use_subpath=use_subpath) def _makeEnviron(self, **extras): environ = { @@ -207,10 +208,10 @@ class Test_static_view(unittest.TestCase): environ.update(extras) return environ - def test_abspath(self): + def test_abspath_subpath(self): import os.path path = os.path.dirname(__file__) - view = self._makeOne(path) + view = self._makeOne(path, use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ['__init__.py'] @@ -219,9 +220,9 @@ class Test_static_view(unittest.TestCase): self.assertEqual(request.copied, True) self.assertEqual(response.directory, os.path.normcase(path)) - def test_relpath(self): + def test_relpath_subpath(self): path = 'fixtures' - view = self._makeOne(path) + view = self._makeOne(path, use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ['__init__.py'] @@ -233,8 +234,22 @@ class Test_static_view(unittest.TestCase): self.assertEqual(response.package_name, 'pyramid.tests') self.assertEqual(response.cache_max_age, 3600) - def test_relpath_withpackage(self): - view = self._makeOne('another:fixtures') + def test_relpath_notsubpath(self): + path = 'fixtures' + view = self._makeOne(path) + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertTrue(not hasattr(request, 'copied')) + self.assertEqual(response.root_resource, 'fixtures') + self.assertEqual(response.resource_name, 'fixtures') + self.assertEqual(response.package_name, 'pyramid.tests') + self.assertEqual(response.cache_max_age, 3600) + + def test_relpath_withpackage_subpath(self): + view = self._makeOne('another:fixtures', use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ['__init__.py'] @@ -246,8 +261,9 @@ class Test_static_view(unittest.TestCase): self.assertEqual(response.package_name, 'another') self.assertEqual(response.cache_max_age, 3600) - def test_relpath_withpackage_name(self): - view = self._makeOne('fixtures', package_name='another') + def test_relpath_withpackage_name_subpath(self): + view = self._makeOne('fixtures', package_name='another', + use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ['__init__.py'] @@ -259,8 +275,9 @@ class Test_static_view(unittest.TestCase): self.assertEqual(response.package_name, 'another') self.assertEqual(response.cache_max_age, 3600) - def test_no_subpath_preserves_path_info_and_script_name(self): - view = self._makeOne('fixtures', package_name='another') + def test_no_subpath_preserves_path_info_and_script_name_subpath(self): + view = self._makeOne('fixtures', package_name='another', + use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = () @@ -272,8 +289,9 @@ class Test_static_view(unittest.TestCase): self.assertEqual(request.environ['SCRIPT_NAME'], '/script_name/path_info') - def test_with_subpath_path_info_ends_with_slash(self): - view = self._makeOne('fixtures', package_name='another') + def test_with_subpath_path_info_ends_with_slash_subpath(self): + view = self._makeOne('fixtures', package_name='another', + use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ('subpath',) @@ -284,7 +302,8 @@ class Test_static_view(unittest.TestCase): self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info') def test_with_subpath_original_script_name_preserved(self): - view = self._makeOne('fixtures', package_name='another') + view = self._makeOne('fixtures', package_name='another', + use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ('subpath',) @@ -297,7 +316,8 @@ class Test_static_view(unittest.TestCase): '/scriptname/path_info') def test_with_subpath_new_script_name_fixes_trailing_slashes(self): - view = self._makeOne('fixtures', package_name='another') + view = self._makeOne('fixtures', package_name='another', + use_subpath=True) context = DummyContext() request = DummyRequest() request.subpath = ('sub', 'path') diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index d46cfb3f5..8e5861e7b 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -565,6 +565,48 @@ class Test_patch_mimetypes(unittest.TestCase): result = self._callFUT(module) self.assertEqual(result, False) +class Test_static(unittest.TestCase): + def setUp(self): + from zope.deprecation import __show__ + __show__.off() + + def tearDown(self): + from zope.deprecation import __show__ + __show__.on() + + def _getTargetClass(self): + from pyramid.view import static + return static + + def _makeOne(self, path, package_name=None): + return self._getTargetClass()(path, package_name=package_name) + + def _makeEnviron(self, **extras): + environ = { + 'wsgi.url_scheme':'http', + 'wsgi.version':(1,0), + 'SERVER_NAME':'localhost', + 'SERVER_PORT':'8080', + 'REQUEST_METHOD':'GET', + } + environ.update(extras) + return environ + + + def test_relpath_subpath(self): + path = 'fixtures' + view = self._makeOne(path) + context = DummyContext() + request = DummyRequest() + request.subpath = ['__init__.py'] + request.environ = self._makeEnviron() + response = view(context, request) + self.assertEqual(request.copied, True) + self.assertEqual(response.root_resource, 'fixtures') + self.assertEqual(response.resource_name, 'fixtures') + self.assertEqual(response.package_name, 'pyramid.tests') + self.assertEqual(response.cache_max_age, 3600) + class ExceptionResponse(Exception): status = '404 Not Found' app_iter = ['Not Found'] @@ -581,6 +623,18 @@ def make_view(response): class DummyRequest: exception = None + def __init__(self, environ=None): + if environ is None: + environ = {} + self.environ = environ + + def get_response(self, application): + return application + + def copy(self): + self.copied = True + return self + from pyramid.interfaces import IResponse from zope.interface import implements diff --git a/pyramid/view.py b/pyramid/view.py index 6b28601e2..1b59a2ed9 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -1,5 +1,6 @@ import mimetypes import venusian +import warnings from zope.interface import providedBy from zope.deprecation import deprecated @@ -11,6 +12,7 @@ from pyramid.interfaces import IRendererInfo from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import default_exceptionresponse_view +from pyramid.path import caller_package from pyramid.renderers import RendererHelper from pyramid.static import static_view from pyramid.threadlocal import get_current_registry @@ -30,12 +32,27 @@ def init_mimetypes(mimetypes): # fallout. init_mimetypes(mimetypes) -# Nasty BW compat hack: dont yet deprecate this (ever?) -class static(static_view): # only subclass for purposes of autodoc - __doc__ = static_view.__doc__ - _marker = object() +class static(static_view): + """ Backwards compatibility alias for + :class:`pyramid.static.static_view`; it overrides that class' constructor + to pass ``use_subpath=True`` by default. This class is deprecated as of + :app:`Pyramid` 1.1. Use :class:`pyramid.static.static_view` instead + (probably with a ``use_subpath=True`` argument). + """ + def __init__(self, root_dir, cache_max_age=3600, package_name=None): + if package_name is None: + package_name = caller_package().__name__ + static_view.__init__(self, root_dir, cache_max_age=cache_max_age, + package_name=package_name, use_subpath=True) + +deprecated( + 'static', + 'The "pyramid.view.static" class is deprecated as of Pyramid 1.1; ' + 'use the "pyramid.static.static_view" class instead with the ' + '"use_subpath" argument set to True.') + def render_view_to_response(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` -- cgit v1.2.3 From 100a571ebbbe7b9f7143ac6015a1b870191683d5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 01:15:40 -0400 Subject: forgot to change whatsnew --- CHANGES.txt | 12 ++++++------ docs/whatsnew-1.1.rst | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d898c5ca6..f1c153f3f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,12 +9,12 @@ Features https://github.com/Pylons/pyramid/pull/234. - New API class: ``pyramid.static.static_view``. This supersedes the - deprecated ``pyramid.view.static`` class. ``pyramid.satic.static_view`` by - default serves up documents as the result of the request's ``path_info``, - attribute rather than it's ``subpath`` attribute (the inverse was true of - ``pyramid.view.static``, and still is). ``pyramid.static.static_view`` - exposes a ``use_subpath`` flag for use when you don't want the static view - to behave like the older deprecated version. + deprecated ``pyramid.view.static`` class. ``pyramid.static.static_view`` + by default serves up documents as the result of the request's + ``path_info``, attribute rather than it's ``subpath`` attribute (the + inverse was true of ``pyramid.view.static``, and still is). + ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when + you don't want the static view to behave like the older deprecated version. Deprecations ------------ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 20b346090..845170831 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -251,6 +251,16 @@ Minor Feature Additions preprocessor to be specified as a Python callable or Python dotted name. See https://github.com/Pylons/pyramid/pull/183 for rationale. +- New API class: :class:`pyramid.static.static_view`. This supersedes the + (now deprecated) :class:`pyramid.view.static` class. + :class:`pyramid.static.static_view`, by default, serves up documents as the + result of the request's ``path_info``, attribute rather than it's + ``subpath`` attribute (the inverse was true of + :class:`pyramid.view.static`, and still is). + :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use + when you don't want the static view to behave like the older deprecated + version. + Backwards Incompatibilities --------------------------- @@ -323,6 +333,11 @@ Deprecations and Behavior Differences and 2.6 show deprecation warnings by default, so this is unecessary there. All deprecation warnings are emitted to the console. +- The :class:`pyramid.view.static` class has been deprecated in favor of the + newer :class:`pyramid.static.static_view` class. A deprecation warning is + raised when it is used. You should replace it with a reference to + :class:`pyramid.static.static_view` with the ``use_subpath=True`` argument. + - The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands now take a single argument in the form ``/path/to/config.ini#sectionname`` rather than the previous 2-argument spelling ``/path/to/config.ini -- cgit v1.2.3 From c425a46b73a96c484de50dd9eea1595389f37b3d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 01:18:02 -0400 Subject: promote http_cache to major feature --- docs/whatsnew-1.1.rst | 120 ++++++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 845170831..388c40f46 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -29,6 +29,9 @@ The major feature additions in Pyramid 1.1 are: - Default HTTP exception view. +- ``http_cache`` view configuration parameter causes Pyramid to set HTTP + caching headers. + ``request.response`` ~~~~~~~~~~~~~~~~~~~~ @@ -91,6 +94,66 @@ Default HTTP Exception View exception to that of Pyramid 1.0 (the exception will propagate to middleware and to the WSGI server). +``http_cache`` +~~~~~~~~~~~~~~ + +A new value ``http_cache`` can be used as a :term:`view configuration` +parameter. + +When you supply an ``http_cache`` value to a view configuration, the +``Expires`` and ``Cache-Control`` headers of a response generated by the +associated view callable are modified. The value for ``http_cache`` may be +one of the following: + +- A nonzero integer. If it's a nonzero integer, it's treated as a number + of seconds. This number of seconds will be used to compute the + ``Expires`` header and the ``Cache-Control: max-age`` parameter of + responses to requests which call this view. For example: + ``http_cache=3600`` instructs the requesting browser to 'cache this + response for an hour, please'. + +- A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` + instance, it will be converted into a number of seconds, and that number + of seconds will be used to compute the ``Expires`` header and the + ``Cache-Control: max-age`` parameter of responses to requests which call + this view. For example: ``http_cache=datetime.timedelta(days=1)`` + instructs the requesting browser to 'cache this response for a day, + please'. + +- Zero (``0``). If the value is zero, the ``Cache-Control`` and + ``Expires`` headers present in all responses from this view will be + composed such that client browser cache (and any intermediate caches) are + instructed to never cache the response. + +- A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, + {'public':True})``), the first value in the tuple may be a nonzero + integer or a ``datetime.timedelta`` instance; in either case this value + will be used as the number of seconds to cache the response. The second + value in the tuple must be a dictionary. The values present in the + dictionary will be used as input to the ``Cache-Control`` response + header. For example: ``http_cache=(3600, {'public':True})`` means 'cache + for an hour, and add ``public`` to the Cache-Control header of the + response'. All keys and values supported by the + ``webob.cachecontrol.CacheControl`` interface may be added to the + dictionary. Supplying ``{'public':True}`` is equivalent to calling + ``response.cache_control.public = True``. + +Providing a non-tuple value as ``http_cache`` is equivalent to calling +``response.cache_expires(value)`` within your view's body. + +Providing a two-tuple value as ``http_cache`` is equivalent to calling +``response.cache_expires(value[0], **value[1])`` within your view's body. + +If you wish to avoid influencing, the ``Expires`` header, and instead wish +to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` +with the first element of ``None``, e.g.: ``(None, {'public':True})``. + +The environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and configuration +file value ``prevent_http_cache`` are synomymous and allow you to prevent +HTTP cache headers from being set by Pyramid's ``http_cache`` machinery +globally in a process. see :ref:`influencing_http_caching` and +:ref:`preventing_http_caching`. + Minor Feature Additions ----------------------- @@ -116,63 +179,6 @@ Minor Feature Additions JSON-decoded variant of the request body. If the request body is not well-formed JSON, this property will raise an exception. -- A new value ``http_cache`` can be used as a :term:`view configuration` - parameter. - - When you supply an ``http_cache`` value to a view configuration, the - ``Expires`` and ``Cache-Control`` headers of a response generated by the - associated view callable are modified. The value for ``http_cache`` may be - one of the following: - - - A nonzero integer. If it's a nonzero integer, it's treated as a number - of seconds. This number of seconds will be used to compute the - ``Expires`` header and the ``Cache-Control: max-age`` parameter of - responses to requests which call this view. For example: - ``http_cache=3600`` instructs the requesting browser to 'cache this - response for an hour, please'. - - - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` - instance, it will be converted into a number of seconds, and that number - of seconds will be used to compute the ``Expires`` header and the - ``Cache-Control: max-age`` parameter of responses to requests which call - this view. For example: ``http_cache=datetime.timedelta(days=1)`` - instructs the requesting browser to 'cache this response for a day, - please'. - - - Zero (``0``). If the value is zero, the ``Cache-Control`` and - ``Expires`` headers present in all responses from this view will be - composed such that client browser cache (and any intermediate caches) are - instructed to never cache the response. - - - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, - {'public':True})``), the first value in the tuple may be a nonzero - integer or a ``datetime.timedelta`` instance; in either case this value - will be used as the number of seconds to cache the response. The second - value in the tuple must be a dictionary. The values present in the - dictionary will be used as input to the ``Cache-Control`` response - header. For example: ``http_cache=(3600, {'public':True})`` means 'cache - for an hour, and add ``public`` to the Cache-Control header of the - response'. All keys and values supported by the - ``webob.cachecontrol.CacheControl`` interface may be added to the - dictionary. Supplying ``{'public':True}`` is equivalent to calling - ``response.cache_control.public = True``. - - Providing a non-tuple value as ``http_cache`` is equivalent to calling - ``response.cache_expires(value)`` within your view's body. - - Providing a two-tuple value as ``http_cache`` is equivalent to calling - ``response.cache_expires(value[0], **value[1])`` within your view's body. - - If you wish to avoid influencing, the ``Expires`` header, and instead wish - to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` - with the first element of ``None``, e.g.: ``(None, {'public':True})``. - - The environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and configuration - file value ``prevent_http_cache`` are synomymous and allow you to prevent - HTTP cache headers from being set by Pyramid's ``http_cache`` machinery - globally in a process. see :ref:`influencing_http_caching` and - :ref:`preventing_http_caching`. - - A `JSONP `_ renderer. See :ref:`jsonp_renderer` for more details. -- cgit v1.2.3 From 10408c799257d6727c968d63cf439b1ebfbfd335 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 01:18:46 -0400 Subject: typo --- CHANGES.txt | 2 +- docs/whatsnew-1.1.rst | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f1c153f3f..1a0573278 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,7 +14,7 @@ Features ``path_info``, attribute rather than it's ``subpath`` attribute (the inverse was true of ``pyramid.view.static``, and still is). ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when - you don't want the static view to behave like the older deprecated version. + you want the static view to behave like the older deprecated version. Deprecations ------------ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 388c40f46..dd4d488a0 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -264,8 +264,7 @@ Minor Feature Additions ``subpath`` attribute (the inverse was true of :class:`pyramid.view.static`, and still is). :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use - when you don't want the static view to behave like the older deprecated - version. + when you want the static view to behave like the older deprecated version. Backwards Incompatibilities --------------------------- -- cgit v1.2.3 From 9343b6f9f2e243f878ccf8d126ff9cc4bd5878ee Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Thu, 14 Jul 2011 09:18:44 -0700 Subject: eliminated repeated word --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 0c591f6d1..5fad9e227 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -277,7 +277,7 @@ For example: catchall /*subpath ``paster proutes`` generates a table. The table has three columns: a Name -name column, a Pattern column, and a View column. The items listed in the +column, a Pattern column, and a View column. The items listed in the Name column are route names, the items listen in the Pattern column are route patterns, and the items listed in the View column are representations of the view callable that will be invoked when a request matches the associated -- cgit v1.2.3 From f7afa751d7ca36a97474099c30923eeeade33b03 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Thu, 14 Jul 2011 09:19:51 -0700 Subject: corrected typo --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 5fad9e227..68078ab70 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -278,7 +278,7 @@ For example: ``paster proutes`` generates a table. The table has three columns: a Name column, a Pattern column, and a View column. The items listed in the -Name column are route names, the items listen in the Pattern column are route +Name column are route names, the items listed in the Pattern column are route patterns, and the items listed in the View column are representations of the view callable that will be invoked when a request matches the associated route pattern. The view column may show ``None`` if no associated view -- cgit v1.2.3 From 01895d10b5de268089ebf4f0f4ab2812c1d0cc85 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Thu, 14 Jul 2011 11:07:39 -0700 Subject: Removed superfluous 'and' --- docs/narr/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 837db5a94..be35d9fc2 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -14,7 +14,7 @@ run :app:`Pyramid`. .. sidebar:: Python Versions - As of this writing, :app:`Pyramid` has been tested under Python 2.5.5 and + As of this writing, :app:`Pyramid` has been tested under Python 2.5.5, Python 2.6.6, and Python 2.7.2. :app:`Pyramid` does not run under any version of Python before 2.5, and does not yet run under Python 3.X. -- cgit v1.2.3 From 11027dc3d17dc687a51551dddb37da929ca2c0ba Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Thu, 14 Jul 2011 11:12:39 -0700 Subject: Sentence was badly structured and also had a word capitalized after a comma --- docs/narr/install.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index be35d9fc2..5b9e22182 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -73,9 +73,9 @@ manager. For example, this works to do so on an Ubuntu Linux system: On Mac OS X, installing `XCode `_ has much the same effect. -Once you've got development tools installed on your system, On the -same system, to install a Python 2.6 interpreter from *source*, use -the following commands: +Once you've got development tools installed on your system, you can +install a Python 2.6 interpreter from *source*, on the same system, +using the following commands: .. code-block:: text -- cgit v1.2.3 From 5b5cd6fa80421b594fa14a75f3daf6a5703f1898 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 17:35:00 -0400 Subject: - Without a mo-file loaded for the combination of domain/locale, ``pyramid.i18n.Localizer.pluralize`` run using that domain/locale combination raised an inscrutable "translations object has no attr 'plural' error. Now, instead it "works" (it uses a germanic pluralization by default). This is not the "right" thing really (it's nonsensical to try to pluralize something without translations for that locale/domain available), but it matches the behavior of ``pyramid.i18n.Localizer.translate`` so it's at least consistent; see https://github.com/Pylons/pyramid/issues/235. Closes #235. --- CHANGES.txt | 12 ++++++++++++ pyramid/i18n.py | 5 +++++ pyramid/tests/test_i18n.py | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1a0573278..02c2da33d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,18 @@ Deprecations it is used. You should replace it with a reference to ``pyramid.static.static_view`` with the ``use_subpath=True`` argument. +Bug Fixes +--------- + +- Without a mo-file loaded for the combination of domain/locale, + ``pyramid.i18n.Localizer.pluralize`` run using that domain/locale + combination raised an inscrutable "translations object has no attr 'plural' + error. Now, instead it "works" (it uses a germanic pluralization by + default). This is not the "right" thing really (it's nonsensical to try to + pluralize something without translations for that locale/domain available), + but it matches the behavior of ``pyramid.i18n.Localizer.translate`` so it's + at least consistent; see https://github.com/Pylons/pyramid/issues/235. + 1.1b2 (2011-07-13) ================== diff --git a/pyramid/i18n.py b/pyramid/i18n.py index 7bc096bb1..4b34534af 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -214,6 +214,11 @@ class Translations(gettext.GNUTranslations, object): :param fileobj: the file-like object the translation should be read from """ + # germanic plural by default; self.plural will be overwritten by + # GNUTranslations._parse (called as a side effect if fileobj is + # passed to GNUTranslations.__init__) with a "real" self.plural for + # this domain; see https://github.com/Pylons/pyramid/issues/235 + self.plural = lambda n: int(n != 1) gettext.GNUTranslations.__init__(self, fp=fileobj) self.files = filter(None, [getattr(fileobj, 'name', None)]) self.domain = domain diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py index 97117a8cd..fcc41b08e 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -65,6 +65,19 @@ class TestLocalizer(unittest.TestCase): ) self.assertTrue(localizer.pluralizer is pluralizer) + def test_pluralize_default_translations(self): + # test that even without message ids loaded that + # "localizer.pluralize" "works" instead of raising an inscrutable + # "translations object has no attr 'plural' error; see + # see https://github.com/Pylons/pyramid/issues/235 + from pyramid.i18n import Translations + translations = Translations() + translations._catalog = {} + localizer = self._makeOne(None, translations) + result = localizer.pluralize('singular', 'plural', 2, domain='1', + mapping={}) + self.assertEqual(result, 'plural') + class Test_negotiate_locale_name(unittest.TestCase): def setUp(self): cleanUp() @@ -469,6 +482,12 @@ class TestTranslations(unittest.TestCase): self.assertEqual(t.dungettext('messages', 'foo1', 'foos1', 1), 'Voh1') self.assertEqual(t.dungettext('messages1', 'foo1', 'foos1', 1), 'VohD1') + def test_default_germanic_pluralization(self): + t = self._getTargetClass()() + t._catalog = {} + result = t.dungettext('messages', 'foo1', 'foos1', 2) + self.assertEqual(result, 'foos1') + class DummyRequest(object): def __init__(self): -- cgit v1.2.3 From acc2d3ade4f5016ffd2f76923d3f56310b2ba577 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 17:36:35 -0400 Subject: typo --- CHANGES.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 02c2da33d..bc906772d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -29,12 +29,12 @@ Bug Fixes - Without a mo-file loaded for the combination of domain/locale, ``pyramid.i18n.Localizer.pluralize`` run using that domain/locale - combination raised an inscrutable "translations object has no attr 'plural' - error. Now, instead it "works" (it uses a germanic pluralization by - default). This is not the "right" thing really (it's nonsensical to try to - pluralize something without translations for that locale/domain available), - but it matches the behavior of ``pyramid.i18n.Localizer.translate`` so it's - at least consistent; see https://github.com/Pylons/pyramid/issues/235. + combination raised an inscrutable "translations object has no attr + 'plural'" error. Now, instead it "works" (it uses a germanic pluralization + by default). It's nonsensical to try to pluralize something without + translations for that locale/domain available, but this behavior matches + the behavior of ``pyramid.i18n.Localizer.translate`` so it's at least + consistent; see https://github.com/Pylons/pyramid/issues/235. 1.1b2 (2011-07-13) ================== -- cgit v1.2.3 From 0a585a85a73e276cc4c3e75beb040e4abf952e37 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 18:18:10 -0400 Subject: note that flask does not use import ordering, but complexity ordering --- docs/designdefense.rst | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index b285524c6..cc46462da 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1395,8 +1395,8 @@ predictability. a registry in another module. This has the effect that double-registrations will never be performed. -Routes (Usually) Need Relative Ordering -+++++++++++++++++++++++++++++++++++++++ +Routes Need Relative Ordering ++++++++++++++++++++++++++++++ Consider the following simple `Groundhog `_ application: @@ -1471,18 +1471,36 @@ the view associated with the ``/:action`` routing pattern will be invoked: it matches first. A 404 error is raised. This is not what we wanted; it just happened due to the order in which we defined our view functions. -You may be willing to maintain an ordering of your view functions which -reifies your routing policy. Your application may be small enough where this -will never cause an issue. If it becomes large enough to matter, however, I -don't envy you. Maintaining that ordering as your application grows larger -will be difficult. At some point, you will also need to start controlling -*import* ordering as well as function definition ordering. When your -application grows beyond the size of a single file, and when decorators are -used to register views, the non-``__main__`` modules which contain -configuration decorators must be imported somehow for their configuration to -be executed. - -Does that make you a little uncomfortable? It should, because +This is because "Groundhog" routes are added to the routing map in import +order, and matched in the same order when a request comes in. Bottle, like +Groundhog, as of this writing, matches routes in the order in which they're +defined at Python execution time. Flask, on the other hand, does not order +route matching based on import order; it reorders the routes you add to your +application based on their "complexity". Other microframeworks have varying +strategies to do route ordering. + +Your application may be small enough where route ordering will never cause an +issue. If your application becomes large enough, however, being able to +specify or predict that ordering as your application grows larger will be +difficult. At some point, you will likely need to more explicitly start +controlling route ordering, especially in applications that require +extensibility. + +If your microframework orders route matching based on "complexity", you'll +need to understand what that "complexity" ordering is and attempt to inject a +"less complex" route to have it get matched before any "more complex" one to +ensure that it's tried first. + +If your microframework orders its route matching based on relative +import/execution of function decorator definitions, you will need to ensure +you execute all of these statements in the "right" order, and you'll need to +be cognizant of this import/execution ordering as you grow your application +or try to extend it. This is a difficult invariant to maintain for all but +the smallest applications. + +In either case, your application must import the non-``__main__`` modules +which contain configuration decorations somehow for their configuration to be +executed. Does that make you a little uncomfortable? It should, because :ref:`you_dont_own_modulescope`. "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance -- cgit v1.2.3 From 1d197536090ecbd6df2dba96f96233c6526968e9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 18:36:35 -0400 Subject: mention traversal --- docs/designdefense.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index cc46462da..ddc643e09 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1503,6 +1503,13 @@ which contain configuration decorations somehow for their configuration to be executed. Does that make you a little uncomfortable? It should, because :ref:`you_dont_own_modulescope`. +In the meantime, in Pyramid, if you don't want to have to maintain relative +route ordering imperatively, you can use :term:`traversal` instead of route +matching, which is a completely declarative (and completely predictable) +mechanism to map code to URLs. While URL dispatch is easier to understand +for small non-extensible applications, traversal is a great fit for very +large applications and applications that need to be arbitrarily extensible. + "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -- cgit v1.2.3 From 48d4c8afa04d3438e931a686fe54172f4a27345f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 18:41:54 -0400 Subject: we no longer support py24 --- docs/designdefense.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index ddc643e09..22a58155d 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -711,9 +711,9 @@ Pyramid Has Too Many Dependencies This is true. At the time of this writing, the total number of Python package distributions that :app:`Pyramid` depends upon transitively is 18 if -you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or 2.5. This is a lot -more than zero package distribution dependencies: a metric which various -Python microframeworks and Django boast. +you use Python 2.6 or 2.7, or 16 if you use Python 2.5. This is a lot more +than zero package distribution dependencies: a metric which various Python +microframeworks and Django boast. The :mod:`zope.component` and :mod:`zope.configuration` packages on which :app:`Pyramid` depends have transitive dependencies on several other packages -- cgit v1.2.3 From 0428eddb22b02611f1f5e250d916cf705f781aee Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 14 Jul 2011 18:57:40 -0400 Subject: describe what Pyramid does --- docs/designdefense.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 22a58155d..9d3a3d38b 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1503,12 +1503,19 @@ which contain configuration decorations somehow for their configuration to be executed. Does that make you a little uncomfortable? It should, because :ref:`you_dont_own_modulescope`. -In the meantime, in Pyramid, if you don't want to have to maintain relative -route ordering imperatively, you can use :term:`traversal` instead of route -matching, which is a completely declarative (and completely predictable) -mechanism to map code to URLs. While URL dispatch is easier to understand -for small non-extensible applications, traversal is a great fit for very -large applications and applications that need to be arbitrarily extensible. +Pyramid uses neither decorator import time ordering nor does it attempt to +divine the relative "complexity" of one route to another in order to define a +route match ordering. In Pyramid, you have to maintain relative route +ordering imperatively via the chronology of multiple executions of the +:meth:`pyramid.config.Configurator.add_route` method. The order in which you +repeatedly call ``add_route`` becomes the order of route matching. + +If needing to maintain this imperative ordering truly bugs you, you can use +:term:`traversal` instead of route matching, which is a completely +declarative (and completely predictable) mechanism to map code to URLs. +While URL dispatch is easier to understand for small non-extensible +applications, traversal is a great fit for very large applications and +applications that need to be arbitrarily extensible. "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -- cgit v1.2.3 From cab6fc74a00f1e205c717ba08012a3ef0f20e0b4 Mon Sep 17 00:00:00 2001 From: AnneGilles Date: Thu, 14 Jul 2011 16:03:40 -0700 Subject: Edited docs/narr/viewconfig.rst via GitHub --- docs/narr/viewconfig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index a45ebae32..54d3fc4ff 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -11,7 +11,7 @@ View Configuration single: view lookup :term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding -an invoking a :term:`view callable`. :term:`View configuration` controls how +and invoking a :term:`view callable`. :term:`View configuration` controls how :term:`view lookup` operates in your application. During any given request, view configuration information is compared against request data by the view lookup subsystem in order to find the "best" view callable for that request. -- cgit v1.2.3 From 68d12cd78c6406e21e2b861c3fcfd3b37f038953 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 10 Jul 2011 12:38:42 -0500 Subject: Adding a global to track the last registry loaded by Pyramid. --- pyramid/config.py | 14 ++++++++++++++ pyramid/tests/test_config.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/pyramid/config.py b/pyramid/config.py index 44ce5110e..dff88b574 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -5,6 +5,7 @@ import sys import types import traceback import warnings +import weakref import venusian @@ -989,6 +990,14 @@ class Configurator(object): self.registry.notify(ApplicationCreated(app)) finally: self.manager.pop() + + # see the comments on p.config.last_registry to understand why + def cleanup_last_registry(ref): + global last_registry + last_registry = None + global last_registry + last_registry = weakref.ref(self.registry, cleanup_last_registry) + return app @action_method @@ -3318,3 +3327,8 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) +# last_registry is a hack to keep track of the registry for the last Pyramid +# application created. This is useful to access the registry after the app +# itself has been wrapped in a WSGI stack, specifically for scripting +# purposes in pyramid.scripting. +last_registry = None diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 002eab8e8..f49a693f0 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -672,6 +672,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(len(L), 1) def test_make_wsgi_app(self): + import pyramid.config from pyramid.router import Router from pyramid.interfaces import IApplicationCreated manager = DummyThreadLocalManager() @@ -683,9 +684,14 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) self.assertTrue(manager.popped) + self.assertEqual(pyramid.config.last_registry(), app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) + def test_uninitialized_last_registry(self): + import pyramid.config + self.assertEqual(pyramid.config.last_registry, None) + def test_include_with_dotted_name(self): from pyramid import tests config = self._makeOne() -- cgit v1.2.3 From 981c054d746e29f42ac16da48c838729537f2eea Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 04:53:46 -0500 Subject: Added tracking of p.config.global_registries for created apps. --- pyramid/config.py | 56 ++++++++++++++++++++++------ pyramid/tests/test_config.py | 89 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index dff88b574..93ef51163 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1,3 +1,4 @@ +import collections import inspect import os import re @@ -982,6 +983,7 @@ class Configurator(object): self.commit() from pyramid.router import Router # avoid circdep app = Router(self.registry) + global_registries.add(self.registry) # We push the registry on to the stack here in case any code # that depends on the registry threadlocal APIs used in # listeners subscribed to the IApplicationCreated event. @@ -991,13 +993,6 @@ class Configurator(object): finally: self.manager.pop() - # see the comments on p.config.last_registry to understand why - def cleanup_last_registry(ref): - global last_registry - last_registry = None - global last_registry - last_registry = weakref.ref(self.registry, cleanup_last_registry) - return app @action_method @@ -3327,8 +3322,45 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) -# last_registry is a hack to keep track of the registry for the last Pyramid -# application created. This is useful to access the registry after the app -# itself has been wrapped in a WSGI stack, specifically for scripting -# purposes in pyramid.scripting. -last_registry = None +class WeakOrderedSet(object): + """ Maintain a set of items. + + Each item is stored as a weakref to avoid extending their lifetime. + + The values may be iterated over or the last item added may be + accessed via the ``last`` property. + """ + + def __init__(self): + self._items = {} + self._order = [] + + def add(self, item): + """ Add a registry to the set.""" + oid = id(item) + if oid in self._items: + return + def cleanup(ref): + del self._items[oid] + self._order.remove(oid) + ref = weakref.ref(item, cleanup) + self._items[oid] = ref + self._order.append(oid) + + def __len__(self): + return len(self._order) + + def __contains__(self, item): + oid = id(item) + return oid in self._items + + def __iter__(self): + return (self._items[oid]() for oid in self._order) + + @property + def last(self): + if self._order: + oid = self._order[-1] + return self._items[oid]() + +global_registries = WeakOrderedSet() diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index f49a693f0..1e73573ae 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -684,13 +684,30 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) self.assertTrue(manager.popped) - self.assertEqual(pyramid.config.last_registry(), app.registry) + self.assertEqual(pyramid.config.global_registries.last, app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) - def test_uninitialized_last_registry(self): - import pyramid.config - self.assertEqual(pyramid.config.last_registry, None) + def test_global_registries_empty(self): + import gc + from pyramid.config import global_registries + gc.collect() # force weakref updates + self.assertEqual(global_registries.last, None) + + def test_global_registries(self): + import gc + from pyramid.config import global_registries + config1 = self._makeOne() + config1.make_wsgi_app() + self.assertEqual(global_registries.last, config1.registry) + config2 = self._makeOne() + config2.make_wsgi_app() + self.assertEqual(global_registries.last, config2.registry) + self.assertEqual(list(global_registries), + [config1.registry, config2.registry]) + del config2 + gc.collect() # force weakref updates + self.assertEqual(global_registries.last, config1.registry) def test_include_with_dotted_name(self): from pyramid import tests @@ -5307,6 +5324,70 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) +class Test_WeakOrderedSet(unittest.TestCase): + def _makeOne(self): + from pyramid.config import WeakOrderedSet + return WeakOrderedSet() + + def test_empty(self): + wos = self._makeOne() + self.assertEqual(len(wos), 0) + self.assertEqual(wos.last, None) + + def test_add_item(self): + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_add_multiple_items(self): + wos = self._makeOne() + reg1 = DummyRegistry() + reg2 = DummyRegistry() + wos.add(reg1) + wos.add(reg2) + self.assertEqual(len(wos), 2) + self.assertEqual(list(wos), [reg1, reg2]) + self.assert_(reg1 in wos) + self.assert_(reg2 in wos) + self.assertEqual(wos.last, reg2) + + def test_add_duplicate_items(self): + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + wos.add(reg) + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_weakref_removal(self): + import gc + wos = self._makeOne() + reg = DummyRegistry() + wos.add(reg) + del reg + gc.collect() # force gc + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + + def test_last_updated(self): + import gc + wos = self._makeOne() + reg = DummyRegistry() + reg2 = DummyRegistry() + wos.add(reg) + wos.add(reg2) + del reg2 + gc.collect() # force gc + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assertEqual(wos.last, reg) + class DummyRequest: subpath = () matchdict = None -- cgit v1.2.3 From 91cd7e3c00e659391ffc64b19126c1016749bdd5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:00:42 -0500 Subject: Moved the WeakOrderedSet into pyramid.util. --- pyramid/config.py | 44 +---------------------------- pyramid/tests/test_config.py | 64 ------------------------------------------ pyramid/tests/test_util.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ pyramid/util.py | 40 +++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 93ef51163..0ba7fb995 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1,4 +1,3 @@ -import collections import inspect import os import re @@ -6,7 +5,6 @@ import sys import types import traceback import warnings -import weakref import venusian @@ -84,6 +82,7 @@ from pyramid.traversal import find_interface from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver +from pyramid.util import WeakOrderedSet from pyramid.view import render_view_to_response DEFAULT_RENDERERS = ( @@ -3322,45 +3321,4 @@ def isexception(o): (inspect.isclass(o) and (issubclass(o, Exception))) ) -class WeakOrderedSet(object): - """ Maintain a set of items. - - Each item is stored as a weakref to avoid extending their lifetime. - - The values may be iterated over or the last item added may be - accessed via the ``last`` property. - """ - - def __init__(self): - self._items = {} - self._order = [] - - def add(self, item): - """ Add a registry to the set.""" - oid = id(item) - if oid in self._items: - return - def cleanup(ref): - del self._items[oid] - self._order.remove(oid) - ref = weakref.ref(item, cleanup) - self._items[oid] = ref - self._order.append(oid) - - def __len__(self): - return len(self._order) - - def __contains__(self, item): - oid = id(item) - return oid in self._items - - def __iter__(self): - return (self._items[oid]() for oid in self._order) - - @property - def last(self): - if self._order: - oid = self._order[-1] - return self._items[oid]() - global_registries = WeakOrderedSet() diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 1e73573ae..9dd07a65b 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -5324,70 +5324,6 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) -class Test_WeakOrderedSet(unittest.TestCase): - def _makeOne(self): - from pyramid.config import WeakOrderedSet - return WeakOrderedSet() - - def test_empty(self): - wos = self._makeOne() - self.assertEqual(len(wos), 0) - self.assertEqual(wos.last, None) - - def test_add_item(self): - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) - self.assertEqual(wos.last, reg) - - def test_add_multiple_items(self): - wos = self._makeOne() - reg1 = DummyRegistry() - reg2 = DummyRegistry() - wos.add(reg1) - wos.add(reg2) - self.assertEqual(len(wos), 2) - self.assertEqual(list(wos), [reg1, reg2]) - self.assert_(reg1 in wos) - self.assert_(reg2 in wos) - self.assertEqual(wos.last, reg2) - - def test_add_duplicate_items(self): - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - wos.add(reg) - self.assertEqual(len(wos), 1) - self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) - self.assertEqual(wos.last, reg) - - def test_weakref_removal(self): - import gc - wos = self._makeOne() - reg = DummyRegistry() - wos.add(reg) - del reg - gc.collect() # force gc - self.assertEqual(len(wos), 0) - self.assertEqual(list(wos), []) - self.assertEqual(wos.last, None) - - def test_last_updated(self): - import gc - wos = self._makeOne() - reg = DummyRegistry() - reg2 = DummyRegistry() - wos.add(reg) - wos.add(reg2) - del reg2 - gc.collect() # force gc - self.assertEqual(len(wos), 1) - self.assertEqual(list(wos), [reg]) - self.assertEqual(wos.last, reg) - class DummyRequest: subpath = () matchdict = None diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 47aab948a..65aca88b2 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -175,3 +175,69 @@ class TestDottedNameResolver(unittest.TestCase): self.assertEqual(typ.package, None) self.assertEqual(typ.package_name, None) +class Test_WeakOrderedSet(unittest.TestCase): + def _makeOne(self): + from pyramid.config import WeakOrderedSet + return WeakOrderedSet() + + def test_empty(self): + wos = self._makeOne() + self.assertEqual(len(wos), 0) + self.assertEqual(wos.last, None) + + def test_add_item(self): + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_add_multiple_items(self): + wos = self._makeOne() + reg1 = Dummy() + reg2 = Dummy() + wos.add(reg1) + wos.add(reg2) + self.assertEqual(len(wos), 2) + self.assertEqual(list(wos), [reg1, reg2]) + self.assert_(reg1 in wos) + self.assert_(reg2 in wos) + self.assertEqual(wos.last, reg2) + + def test_add_duplicate_items(self): + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + wos.add(reg) + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assert_(reg in wos) + self.assertEqual(wos.last, reg) + + def test_weakref_removal(self): + import gc + wos = self._makeOne() + reg = Dummy() + wos.add(reg) + del reg + gc.collect() # force gc + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + + def test_last_updated(self): + import gc + wos = self._makeOne() + reg = Dummy() + reg2 = Dummy() + wos.add(reg) + wos.add(reg2) + del reg2 + gc.collect() # force gc + self.assertEqual(len(wos), 1) + self.assertEqual(list(wos), [reg]) + self.assertEqual(wos.last, reg) + +class Dummy(object): + pass diff --git a/pyramid/util.py b/pyramid/util.py index 3e6cd2e60..b3fda9016 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,5 +1,6 @@ import pkg_resources import sys +import weakref from pyramid.exceptions import ConfigurationError from pyramid.path import package_of @@ -143,4 +144,43 @@ class DottedNameResolver(object): return self._zope_dottedname_style(dotted) return dotted +class WeakOrderedSet(object): + """ Maintain a set of items. + Each item is stored as a weakref to avoid extending their lifetime. + + The values may be iterated over or the last item added may be + accessed via the ``last`` property. + """ + + def __init__(self): + self._items = {} + self._order = [] + + def add(self, item): + """ Add a registry to the set.""" + oid = id(item) + if oid in self._items: + return + def cleanup(ref): + del self._items[oid] + self._order.remove(oid) + ref = weakref.ref(item, cleanup) + self._items[oid] = ref + self._order.append(oid) + + def __len__(self): + return len(self._order) + + def __contains__(self, item): + oid = id(item) + return oid in self._items + + def __iter__(self): + return (self._items[oid]() for oid in self._order) + + @property + def last(self): + if self._order: + oid = self._order[-1] + return self._items[oid]() -- cgit v1.2.3 From 31c20b20346d93c326159cabe02cff076eb4ce0f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:25:40 -0500 Subject: Added the ability to make a request object for use in scripts. --- pyramid/scripting.py | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index a3ec9bee5..d2495675e 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,3 +1,4 @@ +from pyramid.config import global_registries from pyramid.request import Request from pyramid.interfaces import IRequestFactory @@ -6,16 +7,18 @@ def get_root(app, request=None): :term:`router` instance as the ``app`` argument. The ``root`` returned is the application root object. The ``closer`` returned is a callable (accepting no arguments) that should be called when - your scripting application is finished using the root. If - ``request`` is not None, it is used as the request passed to the - :app:`Pyramid` application root factory. A request is - constructed and passed to the root factory if ``request`` is None.""" - registry = app.registry + your scripting application is finished using the root. + + If ``request`` is not None, it is used as the request passed to the + :app:`Pyramid` application root factory. A request is constructed + using :meth:`pyramid.scripting.make_request` and passed to the root + factory if ``request`` is None.""" + if hasattr(app, 'registry'): + registry = app.registry + else: + registry = global_registries.last if request is None: - request_factory = registry.queryUtility( - IRequestFactory, default=Request) - request = request_factory.blank('/') - request.registry = registry + request = make_request('/', registry) threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default @@ -23,3 +26,29 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer +def make_request(url, registry=None): + """ Return a :meth:`pyramid.request.Request` object anchored at a + given URL. The object returned will be generated from the supplied + registry's :term:`Request Factory` using the + :meth:`pyramid.interfaces.IRequestFactory.blank` method. + + This request object can be passed to + :meth:`pyramid.scripting.get_root` to initialize an application in + preparation for executing a script with a proper environment setup. + URLs can then be generated with the object, as well as rendering + templates. + + If ``registry`` is not supplied, the last registry loaded from + :meth:`pyramid.config.global_registries` will be used. If you have + loaded more than one :app:`Pyramid` application in the current + process, you may not want to use the last registry loaded, thus + you can search the ``global_registries`` and supply the appropriate + one based on your own criteria. + """ + if registry is None: + registry = global_registries.last + request_factory = registry.queryUtility(IRequestFactory, default=Request) + request = request_factory.blank(url) + request.registry = registry + return request + -- cgit v1.2.3 From 37e3bebf0165ac5f32c82c0bc87296e0ca5fefd3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 05:40:06 -0500 Subject: Added some docs for make_request and global_registries. --- docs/api/config.rst | 10 ++++++++++ docs/api/scripting.rst | 2 ++ pyramid/scripting.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 71ef4a746..d021412b8 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -86,3 +86,13 @@ .. automethod:: testing_add_renderer + .. attribute:: global_registries + + A set of registries that have been created for :app:`Pyramid` + applications. The object itself supports iteration and has a + ``last`` property containing the last registry loaded. + + The registries contained in this object are stored as weakrefs, + thus they will only exist for the lifetime of the actual + applications for which they are being used. + diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 9d5bc2e58..2029578ba 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,3 +7,5 @@ .. autofunction:: get_root + .. autofunction:: make_request + diff --git a/pyramid/scripting.py b/pyramid/scripting.py index d2495675e..fe942ceaf 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -39,7 +39,7 @@ def make_request(url, registry=None): templates. If ``registry`` is not supplied, the last registry loaded from - :meth:`pyramid.config.global_registries` will be used. If you have + :attr:`pyramid.config.global_registries` will be used. If you have loaded more than one :app:`Pyramid` application in the current process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate -- cgit v1.2.3 From a02407ee018a17a0186b3e139b15e05f8ff1c795 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 20:30:38 -0500 Subject: Updated scripting test coverage to 100%. --- pyramid/scripting.py | 6 ++-- pyramid/tests/test_scripting.py | 61 ++++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index fe942ceaf..e04c52a08 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -26,9 +26,9 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer -def make_request(url, registry=None): +def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a - given URL. The object returned will be generated from the supplied + given path. The object returned will be generated from the supplied registry's :term:`Request Factory` using the :meth:`pyramid.interfaces.IRequestFactory.blank` method. @@ -48,7 +48,7 @@ def make_request(url, registry=None): if registry is None: registry = global_registries.last request_factory = registry.queryUtility(IRequestFactory, default=Request) - request = request_factory.blank(url) + request = request_factory.blank(path) request.registry = registry return request diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index d2139b7db..6ed1325ce 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -2,11 +2,11 @@ import unittest class TestGetRoot(unittest.TestCase): def _callFUT(self, app, request=None): - from pyramid.paster import get_root + from pyramid.scripting import get_root return get_root(app, request) def test_it_norequest(self): - app = DummyApp() + app = DummyApp(registry=dummy_registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] @@ -17,7 +17,7 @@ class TestGetRoot(unittest.TestCase): self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_withrequest(self): - app = DummyApp() + app = DummyApp(registry=dummy_registry) request = DummyRequest({}) root, closer = self._callFUT(app, request) self.assertEqual(len(app.threadlocal_manager.pushed), 1) @@ -29,24 +29,58 @@ class TestGetRoot(unittest.TestCase): self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_requestfactory_overridden(self): + app = DummyApp(registry=dummy_registry) + root, closer = self._callFUT(app) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['request'].environ['path'], '/') + + def test_it_with_no_registry(self): + from pyramid.config import global_registries app = DummyApp() - request = Dummy() - class DummyFactory(object): - @classmethod - def blank(cls, path): - return request + # keep registry local so that global_registries is cleared after registry = DummyRegistry(DummyFactory) - app.registry = registry + global_registries.add(registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['request'].registry, registry) + +class TestMakeRequest(unittest.TestCase): + def _callFUT(self, path='/', registry=None): + from pyramid.scripting import make_request + return make_request(path, registry) + + def test_it(self): + request = self._callFUT('/', dummy_registry) + self.assertEqual(request.environ['path'], '/') + self.assertEqual(request.registry, dummy_registry) + + def test_it_with_nondefault_path(self): + request = self._callFUT('/users/login', dummy_registry) + self.assertEqual(request.environ['path'], '/users/login') + self.assertEqual(request.registry, dummy_registry) + + def test_it_with_no_registry(self): + from pyramid.config import global_registries + # keep registry local so that global_registries is cleared after + registry = DummyRegistry(DummyFactory) + global_registries.add(registry) + request = self._callFUT() + self.assertEqual(request.environ['path'], '/') + self.assertEqual(request.registry, registry) class Dummy: pass dummy_root = Dummy() +class DummyFactory(object): + @classmethod + def blank(cls, path): + req = DummyRequest({'path': path}) + return req + class DummyRegistry(object): def __init__(self, result=None): self.result = result @@ -54,12 +88,13 @@ class DummyRegistry(object): def queryUtility(self, iface, default=None): return self.result or default -dummy_registry = DummyRegistry() +dummy_registry = DummyRegistry(DummyFactory) class DummyApp: - def __init__(self): - self.registry = dummy_registry + def __init__(self, registry=None): self.threadlocal_manager = DummyThreadLocalManager() + if registry: + self.registry = registry def root_factory(self, environ): return dummy_root -- cgit v1.2.3 From 54376af6ae324606a96bbc92629cfb2b0b375e12 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 11 Jul 2011 21:08:27 -0500 Subject: Reverted get_root back to its behavior of expecting a router instance. --- pyramid/scripting.py | 5 +---- pyramid/tests/test_scripting.py | 22 +++------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index e04c52a08..79523dff1 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -13,10 +13,7 @@ def get_root(app, request=None): :app:`Pyramid` application root factory. A request is constructed using :meth:`pyramid.scripting.make_request` and passed to the root factory if ``request`` is None.""" - if hasattr(app, 'registry'): - registry = app.registry - else: - registry = global_registries.last + registry = app.registry if request is None: request = make_request('/', registry) threadlocals = {'registry':registry, 'request':request} diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 6ed1325ce..315ab222f 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -35,39 +35,23 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') - def test_it_with_no_registry(self): - from pyramid.config import global_registries - app = DummyApp() - # keep registry local so that global_registries is cleared after - registry = DummyRegistry(DummyFactory) - global_registries.add(registry) - root, closer = self._callFUT(app) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['request'].registry, registry) - class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import make_request return make_request(path, registry) - def test_it(self): + def test_it_with_registry(self): request = self._callFUT('/', dummy_registry) self.assertEqual(request.environ['path'], '/') self.assertEqual(request.registry, dummy_registry) - def test_it_with_nondefault_path(self): - request = self._callFUT('/users/login', dummy_registry) - self.assertEqual(request.environ['path'], '/users/login') - self.assertEqual(request.registry, dummy_registry) - def test_it_with_no_registry(self): from pyramid.config import global_registries # keep registry local so that global_registries is cleared after registry = DummyRegistry(DummyFactory) global_registries.add(registry) - request = self._callFUT() - self.assertEqual(request.environ['path'], '/') + request = self._callFUT('/hello') + self.assertEqual(request.environ['path'], '/hello') self.assertEqual(request.registry, registry) class Dummy: -- cgit v1.2.3 From 2e3a01d35413debcaf0081d17b48cfc5c74a6d59 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 12 Jul 2011 00:38:59 -0500 Subject: Just changed some of the docstrings to reference config_uri. --- pyramid/paster.py | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index a5cd63dfb..08a54001c 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -17,17 +17,17 @@ zope.deprecation.deprecated( 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'), ) -def get_app(config_file, name=None, loadapp=loadapp): +def get_app(config_uri, name=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``. + config file specified by ``config_uri``. If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_file#name``. + the ``config_uri`` string expecting the format ``inifile#name``. If no name is found, the name will default to "main".""" - if '#' in config_file: - path, section = config_file.split('#', 1) + if '#' in config_uri: + path, section = config_uri.split('#', 1) else: - path, section = config_file, 'main' + path, section = config_uri, 'main' if name: section = name config_name = 'config:%s' % path @@ -56,18 +56,19 @@ class PShellCommand(PCommand): This command accepts one positional argument: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``config_uri`` -- specifies the PasteDeploy config file to use for the + interactive shell. The format is ``inifile#name``. If the name is left off, ``main`` will be assumed. Example:: $ paster pshell myapp.ini#main - .. note:: You should use a ``section_name`` that refers to the - actual ``app`` section in the config file that points at - your Pyramid app without any middleware wrapping, or this - command will almost certainly fail. + .. note:: If you do not point the loader directly at the section of the + ini file containing your :app:`Pyramid` application, the + command will attempt to find the app for you. If you are + loading a pipeline that contains more than one :app:`Pyramid` + application within it, the loader will use the last one. """ summary = "Open an interactive shell with a Pyramid application loaded" @@ -106,10 +107,10 @@ class PShellCommand(PCommand): IPShell = None cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - app_spec = self.args[0] - config_file = app_spec.split('#', 1)[0] + config_uri = self.args[0] + config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + app = self.get_app(config_uri, loadapp=self.loadapp[0]) # load default globals shell_globals = { @@ -184,8 +185,8 @@ class PRoutesCommand(PCommand): This command accepts one positional argument: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``config_uri`` -- specifies the PasteDeploy config file to use for the + interactive shell. The format is ``inifile#name``. If the name is left off, ``main`` will be assumed. Example:: @@ -218,8 +219,8 @@ class PRoutesCommand(PCommand): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from zope.interface import Interface - app_spec = self.args[0] - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + config_uri = self.args[0] + app = self.get_app(config_uri, loadapp=self.loadapp[0]) registry = app.registry mapper = self._get_mapper(app) if mapper is not None: @@ -253,8 +254,8 @@ class PViewsCommand(PCommand): This command accepts two positional arguments: - ``config_file#section_name`` -- specifies the PasteDeploy config file - to use for the interactive shell. If the section_name is left off, + ``config_uri`` -- specifies the PasteDeploy config file to use for the + interactive shell. The format is ``inifile#name``. If the name is left off, ``main`` will be assumed. ``url`` -- specifies the URL that will be used to find matching views. @@ -465,10 +466,10 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - app_spec, url = self.args + config_uri, url = self.args if not url.startswith('/'): url = '/%s' % url - app = self.get_app(app_spec, loadapp=self.loadapp[0]) + app = self.get_app(config_uri, loadapp=self.loadapp[0]) registry = app.registry view = self._find_view(url, registry) self.out('') -- cgit v1.2.3 From 359906f09a389db4386984c84cc615eb1f033b8c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:33:13 -0500 Subject: Added p.scripting.get_root2 that doesn't require an app arg. --- docs/api/scripting.rst | 2 ++ pyramid/scripting.py | 36 +++++++++++++++++++++++++- pyramid/tests/test_scripting.py | 57 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 2029578ba..3e9a814fc 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,5 +7,7 @@ .. autofunction:: get_root + .. autofunction:: get_root2 + .. autofunction:: make_request diff --git a/pyramid/scripting.py b/pyramid/scripting.py index 79523dff1..cbcba95df 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,6 +1,9 @@ from pyramid.config import global_registries from pyramid.request import Request from pyramid.interfaces import IRequestFactory +from pyramid.interfaces import IRootFactory +from pyramid.threadlocal import manager as threadlocal_manager +from pyramid.traversal import DefaultRootFactory def get_root(app, request=None): """ Return a tuple composed of ``(root, closer)`` when provided a @@ -23,6 +26,38 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer +def get_root2(request=None, registry=None): + """ Return a tuple composed of ``(root, closer)``. The ``root`` + returned is the application's root object. The ``closer`` returned + is a callable (accepting no arguments) that should be called when + your scripting application is finished using the root. + + If ``request`` is None, a default one is constructed using + :meth:`pyramid.scripting.make_request`. It is used as the request + passed to the :app:`Pyramid` application root factory. + + If ``registry`` is not supplied, the last registry loaded from + :attr:`pyramid.config.global_registries` will be used. If you have + loaded more than one :app:`Pyramid` application in the current + process, you may not want to use the last registry loaded, thus + you can search the ``global_registries`` and supply the appropriate + one based on your own criteria. + """ + if registry is None: + registry = getattr(request, 'registry', global_registries.last) + if request is None: + request = make_request('/', registry) + request.registry = registry + threadlocals = {'registry':registry, 'request':request} + threadlocal_manager.push(threadlocals) + def closer(request=request): # keep request alive via this function default + threadlocal_manager.pop() + q = registry.queryUtility + root_factory = registry.queryUtility(IRootFactory, + default=DefaultRootFactory) + root = root_factory(request) + return root, closer + def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied @@ -48,4 +83,3 @@ def make_request(path, registry=None): request = request_factory.blank(path) request.registry = registry return request - diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 315ab222f..9bf57be06 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -35,6 +35,53 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') +class TestGetRoot2(unittest.TestCase): + def _callFUT(self, request=None, registry=None): + from pyramid.scripting import get_root2 + return get_root2(request, registry) + + def _makeRegistry(self): + return DummyRegistry(DummyFactory) + + def setUp(self): + from pyramid.threadlocal import manager + self.manager = manager + self.default = manager.get() + + def tearDown(self): + self.assertEqual(self.default, self.manager.get()) + + def test_it_norequest(self): + registry = self._makeRegistry() + root, closer = self._callFUT(registry=registry) + pushed = self.manager.get() + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (pushed['request'],)) + closer() + + def test_it_withrequest(self): + request = DummyRequest({}) + registry = request.registry = self._makeRegistry() + root, closer = self._callFUT(request) + pushed = self.manager.get() + self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (request,)) + closer() + + def test_it_with_request_and_registry(self): + request = DummyRequest({}) + registry = request.registry = self._makeRegistry() + root, closer = self._callFUT(request, registry) + pushed = self.manager.get() + self.assertEqual(pushed['request'], request) + self.assertEqual(pushed['registry'], registry) + self.assertEqual(pushed['request'].registry, registry) + self.assertEqual(root.a, (request,)) + closer() + class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import make_request @@ -65,12 +112,16 @@ class DummyFactory(object): req = DummyRequest({'path': path}) return req + def __init__(self, *a, **kw): + self.a = a + self.kw = kw + class DummyRegistry(object): - def __init__(self, result=None): - self.result = result + def __init__(self, factory=None): + self.factory = factory def queryUtility(self, iface, default=None): - return self.result or default + return self.factory or default dummy_registry = DummyRegistry(DummyFactory) -- cgit v1.2.3 From c01a8429ca4f385f0443f324c76d57b17d77dedb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:57:00 -0500 Subject: garden --- pyramid/paster.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 08a54001c..7d75def75 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -57,8 +57,8 @@ class PShellCommand(PCommand): This command accepts one positional argument: ``config_uri`` -- specifies the PasteDeploy config file to use for the - interactive shell. The format is ``inifile#name``. If the name is left off, - ``main`` will be assumed. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. Example:: @@ -186,8 +186,8 @@ class PRoutesCommand(PCommand): This command accepts one positional argument: ``config_uri`` -- specifies the PasteDeploy config file to use for the - interactive shell. The format is ``inifile#name``. If the name is left off, - ``main`` will be assumed. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. Example:: @@ -255,8 +255,8 @@ class PViewsCommand(PCommand): This command accepts two positional arguments: ``config_uri`` -- specifies the PasteDeploy config file to use for the - interactive shell. The format is ``inifile#name``. If the name is left off, - ``main`` will be assumed. + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. ``url`` -- specifies the URL that will be used to find matching views. -- cgit v1.2.3 From f422adb9108520182c7eee5128c0f1e1f64d2e17 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 13 Jul 2011 22:57:09 -0500 Subject: Added p.paster.bootstrap for handling simple loading of INI files. --- docs/api/paster.rst | 12 +++--------- pyramid/paster.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 6668f3c77..09e768fae 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -3,14 +3,8 @@ :mod:`pyramid.paster` --------------------------- -.. module:: pyramid.paster +.. automodule:: pyramid.paster -.. function:: get_app(config_file, name=None) - - Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``. - - If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_file#name``. - If no name is found, the name will default to "main". + .. autofunction:: get_app + .. autofunction:: bootstrap diff --git a/pyramid/paster.py b/pyramid/paster.py index 7d75def75..9ebeacab9 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -9,6 +9,7 @@ from paste.deploy import loadapp from paste.script.command import Command from pyramid.scripting import get_root +from pyramid.scripting import get_root2 from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat @@ -35,6 +36,37 @@ def get_app(config_uri, name=None, loadapp=loadapp): app = loadapp(config_name, name=section, relative_to=here_dir) return app +def bootstrap(config_uri, request=None): + """ Load a WSGI application from the PasteDeploy config file specified + by ``config_uri``. + + .. note:: Most operations within :app:`Pyramid` expect to be invoked + within the context of a WSGI request, thus it's important when + loading your application to anchor it when executing scripts + and other code that is not normally invoked during active WSGI + requests. + + .. note:: For a complex config file containing multiple :app:`Pyramid` + applications, this function will setup the environment under + the context of the last-loaded :app:`Pyramid` application. You + may load a specific application yourself by using the + lower-level functions :meth:`pyramid.paster.get_app` and + :meth:`pyramid.scripting.get_root2` in conjunction with + :attr:`pyramid.config.global_registries`. + + ``config_uri`` -- specifies the PasteDeploy config file to use for the + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. + + ``request`` -- specified to anchor the script to a given set of WSGI + parameters. For example, most people would want to specify the host, + scheme and port such that their script will generate URLs in relation + to those parameters. + """ + app = get_app(config_uri) + root, closer = get_root2(request) + return (app, root, closer) + _marker = object() class PCommand(Command): -- cgit v1.2.3 From 6fa3ad096942a171765ffca94d648eed911feefe Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 18:53:20 -0500 Subject: Removed unused variable --- pyramid/scripting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/scripting.py b/pyramid/scripting.py index cbcba95df..bdc287c83 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -52,7 +52,6 @@ def get_root2(request=None, registry=None): threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default threadlocal_manager.pop() - q = registry.queryUtility root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) root = root_factory(request) -- cgit v1.2.3 From 71696bd1a38466033370c3de56c55ebcd64163f2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 18:59:20 -0500 Subject: Modified bootstrap to return a dict. --- pyramid/paster.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 9ebeacab9..578b854a5 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -38,7 +38,9 @@ def get_app(config_uri, name=None, loadapp=loadapp): def bootstrap(config_uri, request=None): """ Load a WSGI application from the PasteDeploy config file specified - by ``config_uri``. + by ``config_uri``. The environment will be configured as if it is + currently serving ``request``, leaving a natural environment in place + to write scripts that can generate URLs and utilize renderers. .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when @@ -65,7 +67,11 @@ def bootstrap(config_uri, request=None): """ app = get_app(config_uri) root, closer = get_root2(request) - return (app, root, closer) + return { + 'app': app, + 'root': root, + 'closer': closer, + } _marker = object() -- cgit v1.2.3 From 00f7c6abb9ed581411044e9aee2f1647cfadfcb7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 19:54:59 -0500 Subject: Added test coverage for p.paster.bootstrap. --- docs/api/paster.rst | 9 ++++++++- pyramid/tests/test_paster.py | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 09e768fae..2a32e07e9 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -5,6 +5,13 @@ .. automodule:: pyramid.paster - .. autofunction:: get_app + .. function:: get_app(config_uri, name=None) + + Return the WSGI application named ``name`` in the PasteDeploy + config file specified by ``config_uri``. + + If the ``name`` is None, this will attempt to parse the name from + the ``config_uri`` string expecting the format ``inifile#name``. + If no name is found, the name will default to "main". .. autofunction:: bootstrap diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index e7a3b7507..df55afcb8 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -928,8 +928,45 @@ class TestGetApp(unittest.TestCase): self.assertEqual(loadapp.section_name, 'yourapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) - - + +class TestBootstrap(unittest.TestCase): + def _callFUT(self, config_uri, request=None): + from pyramid.paster import bootstrap + return bootstrap(config_uri, request) + + def setUp(self): + import pyramid.paster + self.original_get_app = pyramid.paster.get_app + self.original_getroot2 = pyramid.paster.get_root2 + self.app = app = DummyApp() + self.root = root = Dummy() + + class DummyGetApp(object): + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return app + self.get_app = pyramid.paster.get_app = DummyGetApp() + + class DummyGetRoot2(object): + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return (root, lambda: None) + self.getroot = pyramid.paster.get_root2 = DummyGetRoot2() + + def tearDown(self): + import pyramid.paster + pyramid.paster.get_app = self.original_get_app + pyramid.paster.get_root2 = self.original_getroot2 + + def test_it_request_with_registry(self): + request = DummyRequest({}) + request.registry = dummy_registry + result = self._callFUT('/foo/bar/myapp.ini', request) + self.assertEqual(result['app'], self.app) + self.assertEqual(result['root'], self.root) + self.assert_('closer' in result) class Dummy: pass -- cgit v1.2.3 From eff1cb657b787771aeb2ed0be28c3709ae019fc3 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 14 Jul 2011 21:04:32 -0500 Subject: Modified tests to use global_registries.remove() instead of relying on gc. --- pyramid/tests/test_config.py | 9 ++++----- pyramid/tests/test_scripting.py | 1 + pyramid/tests/test_util.py | 19 +++++++++++++------ pyramid/util.py | 19 ++++++++++++++----- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 9dd07a65b..dd5c90bf7 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -687,16 +687,15 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(pyramid.config.global_registries.last, app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) + pyramid.config.global_registries.empty() def test_global_registries_empty(self): - import gc from pyramid.config import global_registries - gc.collect() # force weakref updates self.assertEqual(global_registries.last, None) def test_global_registries(self): - import gc from pyramid.config import global_registries + global_registries.empty() config1 = self._makeOne() config1.make_wsgi_app() self.assertEqual(global_registries.last, config1.registry) @@ -705,9 +704,9 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(global_registries.last, config2.registry) self.assertEqual(list(global_registries), [config1.registry, config2.registry]) - del config2 - gc.collect() # force weakref updates + global_registries.remove(config2.registry) self.assertEqual(global_registries.last, config1.registry) + global_registries.empty() def test_include_with_dotted_name(self): from pyramid import tests diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 9bf57be06..50dbaae85 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -100,6 +100,7 @@ class TestMakeRequest(unittest.TestCase): request = self._callFUT('/hello') self.assertEqual(request.environ['path'], '/hello') self.assertEqual(request.registry, registry) + global_registries.empty() class Dummy: pass diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 65aca88b2..821d1ff31 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -216,28 +216,35 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(wos.last, reg) def test_weakref_removal(self): - import gc wos = self._makeOne() reg = Dummy() wos.add(reg) - del reg - gc.collect() # force gc + wos.remove(reg) self.assertEqual(len(wos), 0) self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) def test_last_updated(self): - import gc wos = self._makeOne() reg = Dummy() reg2 = Dummy() wos.add(reg) wos.add(reg2) - del reg2 - gc.collect() # force gc + wos.remove(reg2) self.assertEqual(len(wos), 1) self.assertEqual(list(wos), [reg]) self.assertEqual(wos.last, reg) + def test_empty(self): + wos = self._makeOne() + reg = Dummy() + reg2 = Dummy() + wos.add(reg) + wos.add(reg2) + wos.empty() + self.assertEqual(len(wos), 0) + self.assertEqual(list(wos), []) + self.assertEqual(wos.last, None) + class Dummy(object): pass diff --git a/pyramid/util.py b/pyramid/util.py index b3fda9016..a4b69ed96 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -158,17 +158,26 @@ class WeakOrderedSet(object): self._order = [] def add(self, item): - """ Add a registry to the set.""" + """ Add an item to the set.""" oid = id(item) if oid in self._items: return - def cleanup(ref): - del self._items[oid] - self._order.remove(oid) - ref = weakref.ref(item, cleanup) + ref = weakref.ref(item, lambda x: self.remove(item)) self._items[oid] = ref self._order.append(oid) + def remove(self, item): + """ Remove an item from the set.""" + oid = id(item) + if oid in self._items: + del self._items[oid] + self._order.remove(oid) + + def empty(self): + """ Clear all objects from the set.""" + self._items = {} + self._order = [] + def __len__(self): return len(self._order) -- cgit v1.2.3 From c38aaf545681738e841351c74f7c687b488fe6c6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 05:00:59 -0400 Subject: deshadow test --- pyramid/tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 821d1ff31..247b61dad 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -180,7 +180,7 @@ class Test_WeakOrderedSet(unittest.TestCase): from pyramid.config import WeakOrderedSet return WeakOrderedSet() - def test_empty(self): + def test_ctor(self): wos = self._makeOne() self.assertEqual(len(wos), 0) self.assertEqual(wos.last, None) -- cgit v1.2.3 From a80206a84476db56d5bfd95a848dfbc28f842958 Mon Sep 17 00:00:00 2001 From: Brian Sutherland Date: Fri, 15 Jul 2011 13:46:48 +0200 Subject: Use assertFalse instead of deprecated failIf --- pyramid/tests/test_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 6cd86901e..55eed50f5 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -418,7 +418,7 @@ class TestRouter(unittest.TestCase): request.response.a = 1 raise KeyError def exc_view(context, request): - self.failIf(hasattr(request.response, 'a')) + self.assertFalse(hasattr(request.response, 'a')) request.response.body = 'OK' return request.response environ = self._makeEnviron() -- cgit v1.2.3 From ed652182eacf9eb274817905bbfb2041d704f194 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 09:24:03 -0400 Subject: fix Jython test failures --- pyramid/tests/test_config.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 002eab8e8..860653e6c 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -5467,11 +5467,16 @@ class DummyRegistry(object): def parse_httpdate(s): import datetime - return datetime.datetime.strptime(s, "%a, %d %b %Y %H:%M:%S %Z") + # cannot use %Z, must use literal GMT; Jython honors timezone + # but CPython does not + return datetime.datetime.strptime(s, "%a, %d %b %Y %H:%M:%S GMT") def assert_similar_datetime(one, two): for attr in ('year', 'month', 'day', 'hour', 'minute'): - assert(getattr(one, attr) == getattr(two, attr)) + one_attr = getattr(one, attr) + two_attr = getattr(two, attr) + if not one_attr == two_attr: + raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr)) from pyramid.interfaces import IResponse class DummyResponse(object): -- cgit v1.2.3 From 153c2b9bce3170d58cafa48d819aef4497159091 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 09:31:34 -0400 Subject: add description of return value --- pyramid/paster.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyramid/paster.py b/pyramid/paster.py index 578b854a5..8211dc637 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -42,6 +42,12 @@ def bootstrap(config_uri, request=None): currently serving ``request``, leaving a natural environment in place to write scripts that can generate URLs and utilize renderers. + This function returns a dictionary with ``app``, ``root`` and ``closer`` + keys. ``app`` is the WSGI app loaded (based on the ``config_uri``), + ``root`` is the traversal root resource of the Pyramid application, and + ``closer`` is a parameterless callback that may be called when your + script is complete (it pops a threadlocal stack). + .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when loading your application to anchor it when executing scripts -- cgit v1.2.3 From c515d77de5b2f62727251ebc32d1292e67811771 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 10:13:07 -0400 Subject: - get_root2 -> prepare - change prepare return value to a dict, and return the registry, request, etc - various docs and changelog entries. --- CHANGES.txt | 33 +++++++++++++++++++++++++++++++++ docs/api/config.rst | 8 +++++--- docs/api/scripting.rst | 2 +- docs/narr/assets.rst | 2 +- docs/whatsnew-1.1.rst | 39 +++++++++++++++++++++++++++++++++++++++ pyramid/paster.py | 11 ++++------- pyramid/scripting.py | 32 ++++++++++++++++++++++---------- pyramid/tests/test_paster.py | 10 +++++----- pyramid/tests/test_scripting.py | 23 +++++++++++++---------- 9 files changed, 123 insertions(+), 37 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bc906772d..0e1f67cdc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,39 @@ Features ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. +- A new API function ``pyramid.paster.bootstrap`` has been added to make + writing scripts that bootstrap a Pyramid environment easier, e.g.:: + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + request = info['request'] + print request.route_url('myroute') + +- A new API function ``pyramid.scripting.prepare`` has been added. It is a + lower-level analogue of ``pyramid.paster.boostrap`` that accepts a request + and a registry instead of a config file argument, and is used for the same + purpose:: + + from pyramid.scripting import prepare + info = prepare(registry=myregistry) + request = info['request'] + print request.route_url('myroute') + +- A new API function ``pyramid.scripting.make_request`` has been added. The + resulting request will have a ``registry`` attribute. It is meant to be + used in conjunction with ``pyramid.scripting.prepare`` and/or + ``pyramid.paster.bootstrap`` (both of which accept a request as an + argument):: + + from pyramid.scripting import make_request + request = make_request('/') + +- New API attribute ``pyramid.config.global_registries`` is an iterable + object that contains references to every Pyramid registry loaded into the + current process via ``pyramid.config.Configurator.make_app``. It also has + a ``last`` attribute containing the last registry loaded. This is used by + the scripting machinery, and is available for introspection. + Deprecations ------------ diff --git a/docs/api/config.rst b/docs/api/config.rst index d021412b8..2c394ac41 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -88,9 +88,11 @@ .. attribute:: global_registries - A set of registries that have been created for :app:`Pyramid` - applications. The object itself supports iteration and has a - ``last`` property containing the last registry loaded. + The set of registries that have been created for :app:`Pyramid` + applications, one per each call to + :meth:`pyramid.config.Configurator.make_app` in the current process. The + object itself supports iteration and has a ``last`` property containing + the last registry loaded. The registries contained in this object are stored as weakrefs, thus they will only exist for the lifetime of the actual diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 3e9a814fc..79136a98b 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -7,7 +7,7 @@ .. autofunction:: get_root - .. autofunction:: get_root2 + .. autofunction:: prepare .. autofunction:: make_request diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index d57687477..f35f6dd7d 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -312,7 +312,7 @@ its behavior is almost exactly the same once it's configured. ``add_view`` (at least those without a ``route_name``). A :class:`~pyramid.static.static_view` static view cannot be made root-relative when you use traversal unless it's registered as a - :term:`NotFound view`. + :term:`Not Found view`. To serve files within a directory located on your filesystem at ``/path/to/static/dir`` as the result of a "catchall" route hanging from the diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index dd4d488a0..32955ab75 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -266,6 +266,45 @@ Minor Feature Additions :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. +- A new API function :func:`pyramid.paster.bootstrap` has been added to make + writing scripts that bootstrap a Pyramid environment easier, e.g.: + + .. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + request = info['request'] + print request.route_url('myroute') + +- A new api function :func:`pyramid.scripting.prepare` has been added. It is + a lower-level analogue of :func:`pyramid.paster.boostrap` that accepts a + request and a registry instead of a config file argument, and is used for + the same purpose: + + .. code-block:: python + + from pyramid.scripting import prepare + info = prepare(registry=myregistry) + request = info['request'] + print request.route_url('myroute') + +- A new API function :func:`pyramid.scripting.make_request` has been added. + The resulting request will have a ``registry`` attribute. It is meant to + be used in conjunction with :func:`pyramid.scripting.prepare` and/or + :func:`pyramid.paster.bootstrap` (both of which accept a request as an + argument): + + .. code-block:: python + + from pyramid.scripting import make_request + request = make_request('/') + +- New API attribute :attr:`pyramid.config.global_registries` is an iterable + object that contains references to every Pyramid registry loaded into the + current process via :meth:`pyramid.config.Configurator.make_app`. It also + has a ``last`` attribute containing the last registry loaded. This is used + by the scripting machinery, and is available for introspection. + Backwards Incompatibilities --------------------------- diff --git a/pyramid/paster.py b/pyramid/paster.py index 8211dc637..b6d7777c0 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -9,7 +9,7 @@ from paste.deploy import loadapp from paste.script.command import Command from pyramid.scripting import get_root -from pyramid.scripting import get_root2 +from pyramid.scripting import prepare from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat @@ -72,12 +72,9 @@ def bootstrap(config_uri, request=None): to those parameters. """ app = get_app(config_uri) - root, closer = get_root2(request) - return { - 'app': app, - 'root': root, - 'closer': closer, - } + info = prepare(request) + info['app'] = app + return info _marker = object() diff --git a/pyramid/scripting.py b/pyramid/scripting.py index bdc287c83..c04915d3a 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -26,15 +26,15 @@ def get_root(app, request=None): root = app.root_factory(request) return root, closer -def get_root2(request=None, registry=None): - """ Return a tuple composed of ``(root, closer)``. The ``root`` - returned is the application's root object. The ``closer`` returned - is a callable (accepting no arguments) that should be called when - your scripting application is finished using the root. +def prepare(request=None, registry=None): + """ This function pushes data onto the Pyramid threadlocal stack (request + and registry), making those objects 'current'. It returns a dictionary + useful for bootstrapping a Pyramid application in a scripting + environment. - If ``request`` is None, a default one is constructed using - :meth:`pyramid.scripting.make_request`. It is used as the request - passed to the :app:`Pyramid` application root factory. + If ``request`` is None, a default request is constructed using + :meth:`pyramid.scripting.make_request`. The request passed to the + :app:`Pyramid` application root factory to compute the root. If ``registry`` is not supplied, the last registry loaded from :attr:`pyramid.config.global_registries` will be used. If you have @@ -42,6 +42,17 @@ def get_root2(request=None, registry=None): process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate one based on your own criteria. + + The function returns a dictionary composed of ``root``, ``closer``, + ``registry``, ``request`` and ``root_factory``. The ``root`` returned is + the application's root resource object. The ``closer`` returned is a + callable (accepting no arguments) that should be called when your + scripting application is finished using the root. ``registry`` is the + registry object passed or the last registry loaded into + :attr:`pyramid.config.global_registries` if no registry is passed. + ``request`` is the request object passed or the constructed request if no + request is passed. ``root_factory`` is the root factory used to + construct the root. """ if registry is None: registry = getattr(request, 'registry', global_registries.last) @@ -50,12 +61,13 @@ def get_root2(request=None, registry=None): request.registry = registry threadlocals = {'registry':registry, 'request':request} threadlocal_manager.push(threadlocals) - def closer(request=request): # keep request alive via this function default + def closer(): threadlocal_manager.pop() root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) root = root_factory(request) - return root, closer + return {'root':root, 'closer':closer, 'registry':registry, + 'request':request, 'root_factory':root_factory} def make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index df55afcb8..7785b006e 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -937,7 +937,7 @@ class TestBootstrap(unittest.TestCase): def setUp(self): import pyramid.paster self.original_get_app = pyramid.paster.get_app - self.original_getroot2 = pyramid.paster.get_root2 + self.original_prepare = pyramid.paster.prepare self.app = app = DummyApp() self.root = root = Dummy() @@ -948,17 +948,17 @@ class TestBootstrap(unittest.TestCase): return app self.get_app = pyramid.paster.get_app = DummyGetApp() - class DummyGetRoot2(object): + class DummyPrepare(object): def __call__(self, *a, **kw): self.a = a self.kw = kw - return (root, lambda: None) - self.getroot = pyramid.paster.get_root2 = DummyGetRoot2() + return {'root':root, 'closer':lambda: None} + self.getroot = pyramid.paster.prepare = DummyPrepare() def tearDown(self): import pyramid.paster pyramid.paster.get_app = self.original_get_app - pyramid.paster.get_root2 = self.original_getroot2 + pyramid.paster.prepare = self.original_prepare def test_it_request_with_registry(self): request = DummyRequest({}) diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index 50dbaae85..ccc6656df 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -1,6 +1,6 @@ import unittest -class TestGetRoot(unittest.TestCase): +class Test_get_root(unittest.TestCase): def _callFUT(self, app, request=None): from pyramid.scripting import get_root return get_root(app, request) @@ -35,10 +35,10 @@ class TestGetRoot(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') -class TestGetRoot2(unittest.TestCase): +class Test_prepare(unittest.TestCase): def _callFUT(self, request=None, registry=None): - from pyramid.scripting import get_root2 - return get_root2(request, registry) + from pyramid.scripting import prepare + return prepare(request, registry) def _makeRegistry(self): return DummyRegistry(DummyFactory) @@ -48,39 +48,42 @@ class TestGetRoot2(unittest.TestCase): self.manager = manager self.default = manager.get() - def tearDown(self): - self.assertEqual(self.default, self.manager.get()) - def test_it_norequest(self): registry = self._makeRegistry() - root, closer = self._callFUT(registry=registry) + info = self._callFUT(registry=registry) + root, closer = info['root'], info['closer'] pushed = self.manager.get() self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (pushed['request'],)) closer() + self.assertEqual(self.default, self.manager.get()) def test_it_withrequest(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() - root, closer = self._callFUT(request) + info = self._callFUT(request=request) + root, closer = info['root'], info['closer'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() + self.assertEqual(self.default, self.manager.get()) def test_it_with_request_and_registry(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() - root, closer = self._callFUT(request, registry) + info = self._callFUT(request=request, registry=registry) + root, closer = info['root'], info['closer'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() + self.assertEqual(self.default, self.manager.get()) class TestMakeRequest(unittest.TestCase): def _callFUT(self, path='/', registry=None): -- cgit v1.2.3 From d8b932c53f2f5023d4e9e79116b853cb00035cb6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 10:18:57 -0400 Subject: silence coverage warning --- pyramid/tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 489134f00..a50434f7e 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -5497,7 +5497,7 @@ def assert_similar_datetime(one, two): for attr in ('year', 'month', 'day', 'hour', 'minute'): one_attr = getattr(one, attr) two_attr = getattr(two, attr) - if not one_attr == two_attr: + if not one_attr == two_attr: # pragma: no cover raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr)) from pyramid.interfaces import IResponse -- cgit v1.2.3 From 999d44cf53e2213be8df881c2b407986b462c79c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 10:40:35 -0400 Subject: prep for 1.1b3 --- 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 0e1f67cdc..00ac78835 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.1b3 (2011-07-15) +================== Features -------- diff --git a/docs/conf.py b/docs/conf.py index f0cf5fd53..5c77e083b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.1b2' +version = '1.1b3' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index 55be461e4..3ba985769 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1b2', + version='1.1b3', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From c63151c07d6f0efccf06b40b39e231945212e7eb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 15:33:46 -0400 Subject: note that make_app mutates global_registries --- pyramid/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 0ba7fb995..4a5514d9a 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -977,8 +977,10 @@ class Configurator(object): def make_wsgi_app(self): """ Commits any pending configuration statements, sends a :class:`pyramid.events.ApplicationCreated` event to all listeners, - and returns a :app:`Pyramid` WSGI application representing the - committed configuration state.""" + adds this configuration's reigstry to + :attr:`pyramid.config.global_registries`, and returns a + :app:`Pyramid` WSGI application representing the committed + configuration state.""" self.commit() from pyramid.router import Router # avoid circdep app = Router(self.registry) -- cgit v1.2.3 From 330c330655d5120b6556025765f3343b7a47ea87 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 15:34:05 -0400 Subject: typo --- pyramid/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config.py b/pyramid/config.py index 4a5514d9a..bc4d4e689 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -977,7 +977,7 @@ class Configurator(object): def make_wsgi_app(self): """ Commits any pending configuration statements, sends a :class:`pyramid.events.ApplicationCreated` event to all listeners, - adds this configuration's reigstry to + adds this configuration's registry to :attr:`pyramid.config.global_registries`, and returns a :app:`Pyramid` WSGI application representing the committed configuration state.""" -- cgit v1.2.3 From 7a5f5612791210081db430aa707ed146a8e2c0e9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 15:40:53 -0400 Subject: remove bogus information about route_name, refer to the right method of Configurator when describing global_registries, add http_cache newness warning --- docs/api/config.rst | 6 +++--- pyramid/config.py | 15 +++------------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 2c394ac41..96e955388 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -90,9 +90,9 @@ The set of registries that have been created for :app:`Pyramid` applications, one per each call to - :meth:`pyramid.config.Configurator.make_app` in the current process. The - object itself supports iteration and has a ``last`` property containing - the last registry loaded. + :meth:`pyramid.config.Configurator.make_wsgi_app` in the current + process. The object itself supports iteration and has a ``last`` + property containing the last registry loaded. The registries contained in this object are stored as weakrefs, thus they will only exist for the lifetime of the actual diff --git a/pyramid/config.py b/pyramid/config.py index bc4d4e689..057282838 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1094,6 +1094,8 @@ class Configurator(object): http_cache + .. note:: This feature is new as of Pyramid 1.1. + When you supply an ``http_cache`` value to a view configuration, the ``Expires`` and ``Cache-Control`` headers of a response generated by the associated view callable are modified. The value @@ -1213,18 +1215,7 @@ class Configurator(object): This value must match the ``name`` of a :term:`route configuration` declaration (see :ref:`urldispatch_chapter`) - that must match before this view will be called. Note that - the ``route`` configuration referred to by ``route_name`` - usually has a ``*traverse`` token in the value of its - ``path``, representing a part of the path that will be used - by :term:`traversal` against the result of the route's - :term:`root factory`. - - .. warning:: Using this argument services an advanced - feature that isn't often used unless you want to perform - traversal *after* a route has matched. See - :ref:`hybrid_chapter` for more information on using this - advanced feature. + that must match before this view will be called. request_type -- cgit v1.2.3 From 3cf8bce5af17652d3c4f2644a03f9e29a11d50e4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 15 Jul 2011 15:48:32 -0400 Subject: add not-for-civilians warnings --- pyramid/config.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 057282838..29da51875 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -463,7 +463,7 @@ class Configurator(object): :meth:`pyramid.config.Configuration.commit` is called (or executed immediately if ``autocommit`` is ``True``). - .. note:: This method is typically only used by :app:`Pyramid` + .. warning:: This method is typically only used by :app:`Pyramid` framework extension authors, not by :app:`Pyramid` application developers. @@ -607,6 +607,10 @@ class Configurator(object): """ Add a directive method to the configurator. + .. warning:: This method is typically only used by :app:`Pyramid` + framework extension authors, not by :app:`Pyramid` application + developers. + Framework extenders can add directive methods to a configurator by instructing their users to call ``config.add_directive('somename', 'some.callable')``. This will make ``some.callable`` accessible as @@ -808,11 +812,14 @@ class Configurator(object): def derive_view(self, view, attr=None, renderer=None): """ - Create a :term:`view callable` using the function, instance, or class (or :term:`dotted Python name` referring to the same) provided as ``view`` object. + .. warning:: This method is typically only used by :app:`Pyramid` + framework extension authors, not by :app:`Pyramid` application + developers. + This is API is useful to framework extenders who create pluggable systems which need to register 'proxy' view callables for functions, instances, or classes which meet the -- cgit v1.2.3 From 5fb458c0dd70096e5d619e42992390bb1af071e1 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 01:08:43 -0400 Subject: - Added a section entitled "Writing a Script" to the "Command-Line Pyramid" chapter. --- CHANGES.txt | 9 +++ docs/narr/commandline.rst | 151 ++++++++++++++++++++++++++++++++++++++++++++++ docs/whatsnew-1.1.rst | 3 + pyramid/paster.py | 3 + 4 files changed, 166 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 00ac78835..afbc12747 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,12 @@ +Next release +============ + +Documentation +------------- + +- Added a section entitled "Writing a Script" to the "Command-Line Pyramid" + chapter. + 1.1b3 (2011-07-15) ================== diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 68078ab70..fccf095d3 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -286,3 +286,154 @@ callable could be found. If no routes are configured within your application, nothing will be printed to the console when ``paster proutes`` is executed. +.. _writing_a_script: + +Writing a Script +---------------- + +All web applications are, at their hearts, systems which accept a request and +return a response. When a request is accepted by a :app:`Pyramid` +application, the system receives state from the request which is later relied +on your application code. For example, one :term:`view callable` may assume +it's working against a request that has a ``request.matchdict`` of a +particular composition, while another assumes a different composition of the +matchdict. + +In the meantime, it's convenient to be able to write a Python script that can +work "in a Pyramid environment", for instance to update database tables used +by your :app:`Pyramid` application. But a "real" Pyramid environment doesn't +have a completely static state independent of a request; your application +(and Pyramid itself) is almost always reliant on being able to obtain +information from a request. When you run a Python script that simply imports +code from your application and tries to run it, there just is no request +data, because there isn't any real web request. Therefore some parts of your +application and some Pyramid APIs will not work. + +For this reason, :app:`Pyramid` makes it possible to run a script in an +environment much like the environment produced when a particular +:term:`request` reaches your :app:`Pyramid` application. This is achieved by +using the :func:`pyramid.paster.bootstrap` command in the body of your +script. + +In the simplest case, :func:`pyramid.paster.bootstrap` can be used with a +single argument, which accepts the :term:`PasteDeploy` ``.ini`` file +representing Pyramid your application configuration as a single argument: + +.. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + print info['request'].route_url('home') + +:func:`pyramid.paster.bootstrap` returns a dictionary containing +framework-related information. This dictionary will always contain a +:term:`request` object as its ``request`` key. + +The following keys are also available in the ``info`` dictionary returned by +:func:`~pyramid.paster.bootstrap`: + +request + + A :class:`pyramid.request.Request` object implying the current request + state for your script. + +app + + The :term:`WSGI` application object generated by bootstrapping. + +root + + The :term:`resource` root of your :app:`Pyramid` application. This is an + object generated by the :term:`root factory` configured in your + application. + +registry + + The :term:`application registry` of your :app:`Pyramid` application. + +closer + + A parameterless callable that can be used to pop an internal + :app:`Pyramid` threadlocal stack (used by + :func:`pyramid.threadlocal.get_current_registry` and + :func:`pyramid.threadlocal.get_current_request`) when your scripting job + is finished. + +Let's assume that the ``/path/to/my/development.ini`` file used in the +example above looks like so: + +.. code-block:: ini + + [pipeline:main] + pipeline = egg:WebError#evalerror + another + + [app:another] + use = egg:MyProject + +The configuration loaded by the above bootstrap example will use the +configuration implied by the ``[pipeline:main]`` section of your +configuration file by default. Specifying ``/path/to/my/development.ini`` is +logically equivalent to specifying ``/path/to/my/development.ini#main``. In +this case, we'll be using a configuration that includes an ``app`` object +which is wrapped in the WebError ``evalerror`` middleware. + +You can also specify a particular *section* of the PasteDeploy ``.ini`` file +to load. By default, Pyramid assumes the section name you want to load is +``main``: + +.. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini#another') + print info['request'].route_url('home') + +The above example specifies the ``another`` ``app``, ``pipeline``, or +``composite`` section of your PasteDeploy configuration file. In the case +that we're using a configuration file that looks like this: + +.. code-block:: ini + + [pipeline:main] + pipeline = egg:WebError#evalerror + another + + [app:another] + use = egg:MyProject + +It will mean that the ``/path/to/my/development.ini#another`` argument passed +to bootstrap will imply the ``[app:another]`` section in our configuration +file. Therefore, it will not wrap the WSGI application present in the info +dictionary as ``app`` using WebError's ``evalerror`` middleware. The ``app`` +object present in the info dictionary returned by +:func:`~pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` +instead. + +By default, Pyramid will general a suitable request object in the ``info`` +dictionary anchored at the root path (``/``). You can alternately supply +your own :class:`pyramid.request.Request` instance to the +:func:`~pyramid.paster.bootstrap` function, to set up request parameters +beforehand: + +.. code-block:: python + + from pyramid.request import Request + request = Request.blank('/another/url') + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini#another', request=request) + print info['request'].path_info # will print '/another/url' + +When your scripting logic finishes, it's good manners (but not required) to +call the ``closer`` callback: + +.. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + + # .. do stuff ... + + info['closer']() + + + diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 32955ab75..a37f03a66 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -583,6 +583,9 @@ Dependency Changes Documentation Enhancements -------------------------- +- Added a section entitled :ref:`writing_a_script` to the "Command-Line + Pyramid" chapter. + - The :ref:`bfg_wiki_tutorial` was updated slightly. - The :ref:`bfg_sql_wiki_tutorial` was updated slightly. diff --git a/pyramid/paster.py b/pyramid/paster.py index b6d7777c0..048f747e8 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -70,6 +70,9 @@ def bootstrap(config_uri, request=None): parameters. For example, most people would want to specify the host, scheme and port such that their script will generate URLs in relation to those parameters. + + See :ref:`writing_a_script` for more information about how to use this + function. """ app = get_app(config_uri) info = prepare(request) -- cgit v1.2.3 From 12382c2b61660f65d7f2dc852102e346ad0d46ed Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 01:18:01 -0400 Subject: note version reqt; fix dangling ref --- docs/narr/commandline.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index fccf095d3..6691ea70a 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -315,6 +315,8 @@ environment much like the environment produced when a particular using the :func:`pyramid.paster.bootstrap` command in the body of your script. +.. note:: This feature is new as of :app:`Pyramid` 1.1. + In the simplest case, :func:`pyramid.paster.bootstrap` can be used with a single argument, which accepts the :term:`PasteDeploy` ``.ini`` file representing Pyramid your application configuration as a single argument: @@ -329,7 +331,7 @@ representing Pyramid your application configuration as a single argument: framework-related information. This dictionary will always contain a :term:`request` object as its ``request`` key. -The following keys are also available in the ``info`` dictionary returned by +The following keys are available in the ``info`` dictionary returned by :func:`~pyramid.paster.bootstrap`: request @@ -379,8 +381,7 @@ this case, we'll be using a configuration that includes an ``app`` object which is wrapped in the WebError ``evalerror`` middleware. You can also specify a particular *section* of the PasteDeploy ``.ini`` file -to load. By default, Pyramid assumes the section name you want to load is -``main``: +to load instead of ``main``: .. code-block:: python @@ -409,9 +410,9 @@ object present in the info dictionary returned by :func:`~pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` instead. -By default, Pyramid will general a suitable request object in the ``info`` -dictionary anchored at the root path (``/``). You can alternately supply -your own :class:`pyramid.request.Request` instance to the +By default, Pyramid will general a request object in the ``info`` dictionary +anchored at the root path (``/``). You can alternately supply your own +:class:`pyramid.request.Request` instance to the :func:`~pyramid.paster.bootstrap` function, to set up request parameters beforehand: -- cgit v1.2.3 From 8a8724de56c8dcce763fdb5630c77cb69a149572 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 01:29:52 -0400 Subject: promote bootstrap to major feature --- docs/narr/commandline.rst | 1 - docs/whatsnew-1.1.rst | 28 ++++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 6691ea70a..b2a921eef 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -437,4 +437,3 @@ call the ``closer`` callback: info['closer']() - diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index a37f03a66..48be2190f 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -32,6 +32,9 @@ The major feature additions in Pyramid 1.1 are: - ``http_cache`` view configuration parameter causes Pyramid to set HTTP caching headers. +- Features that make it easier to write scripts that work in a :app:`Pyramid` + environment. + ``request.response`` ~~~~~~~~~~~~~~~~~~~~ @@ -154,6 +157,21 @@ HTTP cache headers from being set by Pyramid's ``http_cache`` machinery globally in a process. see :ref:`influencing_http_caching` and :ref:`preventing_http_caching`. +Easier Scripting Writing +~~~~~~~~~~~~~~~~~~~~~~~~ + +A new API function :func:`pyramid.paster.bootstrap` has been added to make +writing scripts that need to work under Pyramid environment easier, e.g.: + +.. code-block:: python + + from pyramid.paster import bootstrap + info = bootstrap('/path/to/my/development.ini') + request = info['request'] + print request.route_url('myroute') + +See :ref:`writing_a_script` for more details. + Minor Feature Additions ----------------------- @@ -266,16 +284,6 @@ Minor Feature Additions :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. -- A new API function :func:`pyramid.paster.bootstrap` has been added to make - writing scripts that bootstrap a Pyramid environment easier, e.g.: - - .. code-block:: python - - from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini') - request = info['request'] - print request.route_url('myroute') - - A new api function :func:`pyramid.scripting.prepare` has been added. It is a lower-level analogue of :func:`pyramid.paster.boostrap` that accepts a request and a registry instead of a config file argument, and is used for -- cgit v1.2.3 From 795aec4ba786c4920be60f9ac80e650816ea044f Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 16 Jul 2011 09:39:04 -0700 Subject: typo in func name --- docs/narr/commandline.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index b2a921eef..98b06c1ba 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -332,7 +332,7 @@ framework-related information. This dictionary will always contain a :term:`request` object as its ``request`` key. The following keys are available in the ``info`` dictionary returned by -:func:`~pyramid.paster.bootstrap`: +:func:`pyramid.paster.bootstrap`: request -- cgit v1.2.3 From fcece8fe59e2ba17d54209a296edb70bd0f63b29 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 16 Jul 2011 09:58:19 -0700 Subject: Mmm, maybe the bad func name was pasted from an emacs buffer --- docs/narr/commandline.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 98b06c1ba..bc210904f 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -407,13 +407,13 @@ to bootstrap will imply the ``[app:another]`` section in our configuration file. Therefore, it will not wrap the WSGI application present in the info dictionary as ``app`` using WebError's ``evalerror`` middleware. The ``app`` object present in the info dictionary returned by -:func:`~pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` +:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` instead. By default, Pyramid will general a request object in the ``info`` dictionary anchored at the root path (``/``). You can alternately supply your own :class:`pyramid.request.Request` instance to the -:func:`~pyramid.paster.bootstrap` function, to set up request parameters +:func:`pyramid.paster.bootstrap` function, to set up request parameters beforehand: .. code-block:: python -- cgit v1.2.3 From 4b3ba9a09ba5491b6752315a9c68189f3a9d1780 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 16:47:59 -0400 Subject: - Omit custom environ variables when rendering a custom exception template in ``pyramid.httpexceptions.WSGIHTTPException._set_default_attrs``; stringifying thse may trigger code that should not be executed; see https://github.com/Pylons/pyramid/issues/239 Closes #239 --- CHANGES.txt | 8 ++++++++ pyramid/httpexceptions.py | 5 +++++ pyramid/tests/test_httpexceptions.py | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index afbc12747..e8fce9c8d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,14 @@ Documentation - Added a section entitled "Writing a Script" to the "Command-Line Pyramid" chapter. +Bug Fixes +--------- + +- Omit custom environ variables when rendering a custom exception template in + ``pyramid.httpexceptions.WSGIHTTPException._set_default_attrs``; + stringifying thse may trigger code that should not be executed; see + https://github.com/Pylons/pyramid/issues/239 + 1.1b3 (2011-07-15) ================== diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index 44b854929..4d23db8d2 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -236,6 +236,11 @@ ${body}''') if WSGIHTTPException.body_template_obj is not body_tmpl: # Custom template; add headers to args for k, v in environ.items(): + if (not k.startswith('wsgi.')) and ('.' in k): + # omit custom environ variables, stringifying them may + # trigger code that should not be executed here; see + # https://github.com/Pylons/pyramid/issues/239 + continue args[k] = escape(v) for k, v in self.headers.items(): args[k.lower()] = escape(v) diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py index 203d442f7..7db071d03 100644 --- a/pyramid/tests/test_httpexceptions.py +++ b/pyramid/tests/test_httpexceptions.py @@ -232,6 +232,17 @@ class TestWSGIHTTPException(unittest.TestCase): body = list(exc(environ, start_response))[0] self.assertEqual(body, '200 OK\n\nGET') + def test_custom_body_template_with_custom_variable_doesnt_choke(self): + cls = self._getTargetSubclass() + exc = cls(body_template='${REQUEST_METHOD}') + environ = _makeEnviron() + class Choke(object): + def __str__(self): raise ValueError + environ['gardentheory.user'] = Choke() + start_response = DummyStartResponse() + body = list(exc(environ, start_response))[0] + self.assertEqual(body, '200 OK\n\nGET') + def test_body_template_unicode(self): cls = self._getTargetSubclass() la = unicode('/La Pe\xc3\xb1a', 'utf-8') -- cgit v1.2.3 From 42eaadb95ae2bfabc364ffbfa2769ad131d943be Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 14:05:44 -0500 Subject: garden --- docs/narr/commandline.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index bc210904f..9adb04d4b 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -294,7 +294,7 @@ Writing a Script All web applications are, at their hearts, systems which accept a request and return a response. When a request is accepted by a :app:`Pyramid` application, the system receives state from the request which is later relied -on your application code. For example, one :term:`view callable` may assume +on by your application code. For example, one :term:`view callable` may assume it's working against a request that has a ``request.matchdict`` of a particular composition, while another assumes a different composition of the matchdict. @@ -410,7 +410,7 @@ object present in the info dictionary returned by :func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` instead. -By default, Pyramid will general a request object in the ``info`` dictionary +By default, Pyramid will generate a request object in the ``info`` dictionary anchored at the root path (``/``). You can alternately supply your own :class:`pyramid.request.Request` instance to the :func:`pyramid.paster.bootstrap` function, to set up request parameters -- cgit v1.2.3 From 795ddb42a12777f12fc5605fa908106a8d65cffd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 14:05:53 -0500 Subject: Renamed the 'info' dict to 'env' in scripting. --- docs/narr/commandline.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 9adb04d4b..c43145b4c 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -324,14 +324,14 @@ representing Pyramid your application configuration as a single argument: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini') - print info['request'].route_url('home') + env = bootstrap('/path/to/my/development.ini') + print env['request'].route_url('home') :func:`pyramid.paster.bootstrap` returns a dictionary containing framework-related information. This dictionary will always contain a :term:`request` object as its ``request`` key. -The following keys are available in the ``info`` dictionary returned by +The following keys are available in the ``env`` dictionary returned by :func:`pyramid.paster.bootstrap`: request @@ -386,8 +386,8 @@ to load instead of ``main``: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini#another') - print info['request'].route_url('home') + env = bootstrap('/path/to/my/development.ini#another') + print env['request'].route_url('home') The above example specifies the ``another`` ``app``, ``pipeline``, or ``composite`` section of your PasteDeploy configuration file. In the case @@ -404,13 +404,13 @@ that we're using a configuration file that looks like this: It will mean that the ``/path/to/my/development.ini#another`` argument passed to bootstrap will imply the ``[app:another]`` section in our configuration -file. Therefore, it will not wrap the WSGI application present in the info +file. Therefore, it will not wrap the WSGI application present in the ``env`` dictionary as ``app`` using WebError's ``evalerror`` middleware. The ``app`` -object present in the info dictionary returned by +object present in the ``env`` dictionary returned by :func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` instead. -By default, Pyramid will generate a request object in the ``info`` dictionary +By default, Pyramid will generate a request object in the ``env`` dictionary anchored at the root path (``/``). You can alternately supply your own :class:`pyramid.request.Request` instance to the :func:`pyramid.paster.bootstrap` function, to set up request parameters @@ -421,8 +421,8 @@ beforehand: from pyramid.request import Request request = Request.blank('/another/url') from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini#another', request=request) - print info['request'].path_info # will print '/another/url' + env = bootstrap('/path/to/my/development.ini#another', request=request) + print env['request'].path_info # will print '/another/url' When your scripting logic finishes, it's good manners (but not required) to call the ``closer`` callback: @@ -430,10 +430,10 @@ call the ``closer`` callback: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini') + env = bootstrap('/path/to/my/development.ini') # .. do stuff ... - info['closer']() + env['closer']() -- cgit v1.2.3 From 01c4f3e8253b708df2266e6847ffc649603eec02 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 14:51:11 -0500 Subject: Modified the order of the WeakOrderedSet to remember the most recent. --- pyramid/util.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyramid/util.py b/pyramid/util.py index a4b69ed96..7fd1b0dc6 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -151,6 +151,17 @@ class WeakOrderedSet(object): The values may be iterated over or the last item added may be accessed via the ``last`` property. + + If items are added more than once, the most recent addition will + be remembered in the order: + + order = WeakOrderedSet() + order.add('1') + order.add('2') + order.add('1') + + list(order) == ['2', '1'] + order.last == '1' """ def __init__(self): @@ -161,6 +172,8 @@ class WeakOrderedSet(object): """ Add an item to the set.""" oid = id(item) if oid in self._items: + self._order.remove(oid) + self._order.append(oid) return ref = weakref.ref(item, lambda x: self.remove(item)) self._items[oid] = ref -- cgit v1.2.3 From 8ac4c95d9f8bb32891c21b483474e402ff1c27fe Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 15:19:44 -0500 Subject: Reworked pyramid.scripting. Modified docs and made make_request private. Renamed make_request to _make_request to make clear that it's not a private API. p.scripting.prepare now raises an exception if no valid pyramid app can be found to avoid obscure errors later on. --- docs/api/scripting.rst | 2 -- pyramid/scripting.py | 68 +++++++++++++++++++++++------------------ pyramid/tests/test_scripting.py | 10 ++++-- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 79136a98b..51bd3c7a0 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -9,5 +9,3 @@ .. autofunction:: prepare - .. autofunction:: make_request - diff --git a/pyramid/scripting.py b/pyramid/scripting.py index c04915d3a..47178c22e 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,4 +1,5 @@ from pyramid.config import global_registries +from pyramid.exceptions import ConfigurationError from pyramid.request import Request from pyramid.interfaces import IRequestFactory from pyramid.interfaces import IRootFactory @@ -12,13 +13,14 @@ def get_root(app, request=None): is a callable (accepting no arguments) that should be called when your scripting application is finished using the root. - If ``request`` is not None, it is used as the request passed to the - :app:`Pyramid` application root factory. A request is constructed - using :meth:`pyramid.scripting.make_request` and passed to the root - factory if ``request`` is None.""" + ``request`` is passed to the :app:`Pyramid` application root + factory to compute the root. If ``request`` is None, a default + will be constructed using the registry's :term:`Request Factory` + via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. + """ registry = app.registry if request is None: - request = make_request('/', registry) + request = _make_request('/', registry) threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default @@ -27,37 +29,43 @@ def get_root(app, request=None): return root, closer def prepare(request=None, registry=None): - """ This function pushes data onto the Pyramid threadlocal stack (request - and registry), making those objects 'current'. It returns a dictionary - useful for bootstrapping a Pyramid application in a scripting - environment. + """ This function pushes data onto the Pyramid threadlocal stack + (request and registry), making those objects 'current'. It + returns a dictionary useful for bootstrapping a Pyramid + application in a scripting environment. - If ``request`` is None, a default request is constructed using - :meth:`pyramid.scripting.make_request`. The request passed to the - :app:`Pyramid` application root factory to compute the root. + ``request`` is passed to the :app:`Pyramid` application root + factory to compute the root. If ``request`` is None, a default + will be constructed using the registry's :term:`Request Factory` + via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. If ``registry`` is not supplied, the last registry loaded from - :attr:`pyramid.config.global_registries` will be used. If you have - loaded more than one :app:`Pyramid` application in the current - process, you may not want to use the last registry loaded, thus - you can search the ``global_registries`` and supply the appropriate - one based on your own criteria. + :attr:`pyramid.config.global_registries` will be used. If you + have loaded more than one :app:`Pyramid` application in the + current process, you may not want to use the last registry + loaded, thus you can search the ``global_registries`` and supply + the appropriate one based on your own criteria. - The function returns a dictionary composed of ``root``, ``closer``, - ``registry``, ``request`` and ``root_factory``. The ``root`` returned is - the application's root resource object. The ``closer`` returned is a - callable (accepting no arguments) that should be called when your - scripting application is finished using the root. ``registry`` is the - registry object passed or the last registry loaded into + The function returns a dictionary composed of ``root``, + ``closer``, ``registry``, ``request`` and ``root_factory``. The + ``root`` returned is the application's root resource object. The + ``closer`` returned is a callable (accepting no arguments) that + should be called when your scripting application is finished + using the root. ``registry`` is the registry object passed or + the last registry loaded into :attr:`pyramid.config.global_registries` if no registry is passed. - ``request`` is the request object passed or the constructed request if no - request is passed. ``root_factory`` is the root factory used to - construct the root. + ``request`` is the request object passed or the constructed request + if no request is passed. ``root_factory`` is the root factory used + to construct the root. """ if registry is None: registry = getattr(request, 'registry', global_registries.last) + if registry is None: + raise ConfigurationError('No valid Pyramid applications could be ' + 'found, make sure one has been created ' + 'before trying to activate it.') if request is None: - request = make_request('/', registry) + request = _make_request('/', registry) request.registry = registry threadlocals = {'registry':registry, 'request':request} threadlocal_manager.push(threadlocals) @@ -69,14 +77,14 @@ def prepare(request=None, registry=None): return {'root':root, 'closer':closer, 'registry':registry, 'request':request, 'root_factory':root_factory} -def make_request(path, registry=None): +def _make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied registry's :term:`Request Factory` using the :meth:`pyramid.interfaces.IRequestFactory.blank` method. - This request object can be passed to - :meth:`pyramid.scripting.get_root` to initialize an application in + This request object can be passed to :meth:`pyramid.scripting.get_root` + or :meth:`pyramid.scripting.prepare` to initialize an application in preparation for executing a script with a proper environment setup. URLs can then be generated with the object, as well as rendering templates. diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index ccc6656df..f01e744b8 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -48,6 +48,10 @@ class Test_prepare(unittest.TestCase): self.manager = manager self.default = manager.get() + def test_it_no_valid_apps(self): + from pyramid.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, self._callFUT) + def test_it_norequest(self): registry = self._makeRegistry() info = self._callFUT(registry=registry) @@ -85,10 +89,10 @@ class Test_prepare(unittest.TestCase): closer() self.assertEqual(self.default, self.manager.get()) -class TestMakeRequest(unittest.TestCase): +class Test__make_request(unittest.TestCase): def _callFUT(self, path='/', registry=None): - from pyramid.scripting import make_request - return make_request(path, registry) + from pyramid.scripting import _make_request + return _make_request(path, registry) def test_it_with_registry(self): request = self._callFUT('/', dummy_registry) -- cgit v1.2.3 From bec6d110c2e996db4db0c683e53c0f3913371059 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 16:50:59 -0500 Subject: Updated 'pshell' to support the new bootstrap api. --- pyramid/paster.py | 104 +++++++++++++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 048f747e8..444608f92 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -42,11 +42,12 @@ def bootstrap(config_uri, request=None): currently serving ``request``, leaving a natural environment in place to write scripts that can generate URLs and utilize renderers. - This function returns a dictionary with ``app``, ``root`` and ``closer`` - keys. ``app`` is the WSGI app loaded (based on the ``config_uri``), - ``root`` is the traversal root resource of the Pyramid application, and - ``closer`` is a parameterless callback that may be called when your - script is complete (it pops a threadlocal stack). + This function returns a dictionary with ``app``, ``root``, ``closer``, + ``request``, and ``registry`` keys. ``app`` is the WSGI app loaded + (based on the ``config_uri``), ``root`` is the traversal root resource + of the Pyramid application, and ``closer`` is a parameterless callback + that may be called when your script is complete (it pops a threadlocal + stack). .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when @@ -59,7 +60,7 @@ def bootstrap(config_uri, request=None): the context of the last-loaded :app:`Pyramid` application. You may load a specific application yourself by using the lower-level functions :meth:`pyramid.paster.get_app` and - :meth:`pyramid.scripting.get_root2` in conjunction with + :meth:`pyramid.scripting.prepare` in conjunction with :attr:`pyramid.config.global_registries`. ``config_uri`` -- specifies the PasteDeploy config file to use for the @@ -69,15 +70,17 @@ def bootstrap(config_uri, request=None): ``request`` -- specified to anchor the script to a given set of WSGI parameters. For example, most people would want to specify the host, scheme and port such that their script will generate URLs in relation - to those parameters. + to those parameters. A request with default parameters is constructed + for you if none is provided. You can mutate the request's ``environ`` + later to setup a specific host/port/scheme/etc. See :ref:`writing_a_script` for more information about how to use this function. """ app = get_app(config_uri) - info = prepare(request) - info['app'] = app - return info + env = prepare(request) + env['app'] = app + return env _marker = object() @@ -126,6 +129,7 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") + bootstrap = (bootstrap,) # testing ConfigParser = ConfigParser.ConfigParser # testing def pshell_file_config(self, filename): @@ -149,72 +153,60 @@ class PShellCommand(PCommand): from IPython.Shell import IPShell except ImportError: IPShell = None - cprt = 'Type "help" for more information.' - banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) config_uri = self.args[0] config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(config_uri, loadapp=self.loadapp[0]) + self.pshell_file_config(config_file) - # load default globals - shell_globals = { - 'app': app, - } - default_variables = {'app': 'The WSGI Application'} - if hasattr(app, 'registry'): - root, closer = self.get_root(app) - shell_globals.update({'root':root, 'registry':app.registry, - 'settings': app.registry.settings}) - default_variables.update({ - 'root': 'The root of the default resource tree.', - 'registry': 'The Pyramid registry object.', - 'settings': 'The Pyramid settings object.', - }) - warning = '' - else: - # warn the user that this isn't actually the Pyramid app - warning = """\n -WARNING: You have loaded a generic WSGI application, therefore the "root", -"registry", and "settings" global variables are not available. To correct -this, run "pshell" again and specify the INI section containing your Pyramid -application. For example, if your app is in the '[app:myapp]' config file -section, use 'development.ini#myapp' instead of 'development.ini' or -'development.ini#main'.""" - closer = lambda: None + # bootstrap the environ + env = self.bootstrap[0](config_uri) + + # remove the closer from the env + closer = env.pop('closer') + + # setup help text for default environment + env_help = dict(env) + env_help['app'] = 'The WSGI application.' + env_help['root'] = 'Root of the default resource tree.' + env_help['registry'] = 'Active Pyramid registry.' + env_help['request'] = 'Active request object.' + env_help['root_factory'] = ( + 'Default root factory used to create `root`.') # load the pshell section of the ini file - self.pshell_file_config(config_file) - shell_globals.update(self.loaded_objects) + env.update(self.loaded_objects) - # eliminate duplicates from default_variables + # eliminate duplicates from env, allowing custom vars to override for k in self.loaded_objects: - if k in default_variables: - del default_variables[k] + if k in env_help: + del env_help[k] - # append the loaded variables - if default_variables: - banner += '\n\nDefault Variables:' - for var, txt in default_variables.iteritems(): - banner += '\n %-12s %s' % (var, txt) + # generate help text + help = '\n' + if env_help: + help += 'Environment:' + for var in sorted(env_help.keys()): + help += '\n %-12s %s' % (var, env_help[var]) if self.object_help: - banner += '\n\nCustom Variables:' + help += '\n\nCustom Variables:' for var in sorted(self.object_help.keys()): - banner += '\n %-12s %s' % (var, self.object_help[var]) + help += '\n %-12s %s' % (var, self.object_help[var]) - # append the warning - banner += warning - banner += '\n' + help += '\n' if (IPShell is None) or self.options.disable_ipython: + cprt = 'Type "help" for more information.' + banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) + banner += '\n' + help try: - self.interact[0](banner, local=shell_globals) + self.interact[0](banner, local=env) finally: closer() else: try: - shell = IPShell(argv=[], user_ns=shell_globals) - shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner + shell = IPShell(argv=[], user_ns=env) + shell.IP.BANNER = shell.IP.BANNER + help shell.mainloop() finally: closer() -- cgit v1.2.3 From 0e80413579435b29f3fc493d4af8742e22ff9ede Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 18:04:41 -0500 Subject: Added test coverage for 'PShellCommand'. --- pyramid/tests/test_paster.py | 377 +++++++++++++++---------------------------- 1 file changed, 134 insertions(+), 243 deletions(-) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 7785b006e..4a099bbdb 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -5,245 +5,117 @@ class TestPShellCommand(unittest.TestCase): from pyramid.paster import PShellCommand return PShellCommand - def _makeOne(self): - return self._getTargetClass()('pshell') + def _makeOne(self, patch_interact=True, patch_bootstrap=True, + patch_config=True, patch_args=True, patch_options=True): + cmd = self._getTargetClass()('pshell') + if patch_interact: + self.interact = DummyInteractor() + cmd.interact = (self.interact,) + if patch_bootstrap: + self.bootstrap = DummyBootstrap() + cmd.bootstrap = (self.bootstrap,) + if patch_config: + self.config_factory = DummyConfigParserFactory() + cmd.ConfigParser = self.config_factory + if patch_args: + self.args = ('/foo/bar/myapp.ini#myapp',) + cmd.args = self.args + if patch_options: + class Options(object): pass + self.options = Options() + self.options.disable_ipython = True + cmd.options = self.options + return cmd def test_command_ipshell_is_None_ipython_enabled(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_ipshell_is_not_None_ipython_disabled(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() command.options.disable_ipython = True command.command(IPShell='notnone') - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_ipython_enabled(self): - command = self._makeOne() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() + command = self._makeOne(patch_interact=False) command.options.disable_ipython = False + dummy_shell_factory = DummyIPShellFactory() command.command(IPShell=dummy_shell_factory) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(dummy_shell_factory.shell.local_ns, - {'app':app, 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(dummy_shell_factory.shell.local_ns, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) self.assertEqual(dummy_shell_factory.shell.global_ns, {}) - self.assertTrue('\n\n' in dummy_shell_factory.shell.IP.BANNER) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - - def test_command_get_app_hookable(self): - from paste.deploy import loadapp - command = self._makeOne() - app = DummyApp() - apped = [] - def get_app(*arg, **kw): - apped.append((arg, kw)) - return app - command.get_app = get_app - interact = DummyInteractor() - app = DummyApp() - command.interact = (interact,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython =True - command.command(IPShell=None) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app': app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - self.assertEqual(apped, [(('/foo/bar/myapp.ini#myapp',), - {'loadapp': loadapp})]) - - def test_command_get_root_hookable(self): - command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - root = Dummy() - apps = [] - def get_root(app): - apps.append(app) - return root, lambda *arg: None - command.get_root =get_root - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython =True - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 0) - self.assertEqual(interact.local, {'app':app, - 'root':root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(apps, [app]) + self.assertTrue(self.bootstrap.closer.called) def test_command_loads_custom_items(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) model = Dummy() - command.ConfigParser = makeDummyConfigParser([('m', model)]) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings, - 'm': model}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - - def test_command_no_custom_section(self): - command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser(None) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + self.config_factory.items = [('m', model)] + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'm':model, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_custom_section_override(self): command = self._makeOne() - interact = DummyInteractor() - app = Dummy() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - model = Dummy() - command.ConfigParser = makeDummyConfigParser([('app', model)]) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(interact.local, {'app':model}) - self.assertTrue(interact.banner) - - def test_command_generic_wsgi_app(self): - command = self._makeOne() - interact = DummyInteractor() - app = Dummy() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser(None) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + dummy = Dummy() + self.config_factory.items = [('app', dummy), ('root', dummy), + ('registry', dummy), ('request', dummy)] + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(interact.local, {'app':app}) - self.assertTrue(interact.banner) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':dummy, 'root':dummy, 'registry':dummy, 'request':dummy, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): @@ -1015,22 +887,7 @@ class DummyLoadApp: class DummyApp: def __init__(self): self.registry = dummy_registry - self.threadlocal_manager = DummyThreadLocalManager() - - def root_factory(self, environ): - return dummy_root -class DummyThreadLocalManager: - def __init__(self): - self.pushed = [] - self.popped = [] - - def push(self, item): - self.pushed.append(item) - - def pop(self): - self.popped.append(True) - class DummyMapper(object): def __init__(self, *routes): self.routes = routes @@ -1073,15 +930,49 @@ class DummyMultiView(object): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs -def makeDummyConfigParser(items): - class DummyConfigParser(object): - def read(self, filename): - self.filename = filename - - def items(self, section): - self.section = section - if items is None: - from ConfigParser import NoSectionError - raise NoSectionError, section - return items - return DummyConfigParser +class DummyConfigParser(object): + def __init__(self, result): + self.result = result + + def read(self, filename): + self.filename = filename + + def items(self, section): + self.section = section + if self.result is None: + from ConfigParser import NoSectionError + raise NoSectionError, section + return self.result + +class DummyConfigParserFactory(object): + items = None + + def __call__(self): + self.parser = DummyConfigParser(self.items) + return self.parser + +class DummyCloser(object): + def __call__(self): + self.called = True + +class DummyBootstrap(object): + def __init__(self, app=None, registry=None, request=None, root=None, + root_factory=None, closer=None): + self.app = app or DummyApp() + self.registry = registry or dummy_registry + self.request = request or DummyRequest({}) + self.root = root or dummy_root + self.root_factory = root_factory or Dummy() + self.closer = closer or DummyCloser() + + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return { + 'app': self.app, + 'registry': self.registry, + 'request': self.request, + 'root': self.root, + 'root_factory': self.root_factory, + 'closer': self.closer, + } -- cgit v1.2.3 From 69452f63ab2efa39c9273646959341287ba5ee15 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 16 Jul 2011 18:26:12 -0500 Subject: Changed the URL generation example to be more practical. --- docs/narr/commandline.rst | 60 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index c43145b4c..30e678f07 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -390,39 +390,50 @@ to load instead of ``main``: print env['request'].route_url('home') The above example specifies the ``another`` ``app``, ``pipeline``, or -``composite`` section of your PasteDeploy configuration file. In the case -that we're using a configuration file that looks like this: +``composite`` section of your PasteDeploy configuration file. The ``app`` +object present in the ``env`` dictionary returned by +:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router`. -.. code-block:: ini +Changing the Request +~~~~~~~~~~~~~~~~~~~~ - [pipeline:main] - pipeline = egg:WebError#evalerror - another +By default, Pyramid will generate a request object in the ``env`` dictionary +for the URL ``http://localhost:80/``. This means that any URLs generated +by Pyramid during the execution of your script will be anchored here. This +is generally not what you want. - [app:another] - use = egg:MyProject +So how do we make Pyramid generate the correct URLs? -It will mean that the ``/path/to/my/development.ini#another`` argument passed -to bootstrap will imply the ``[app:another]`` section in our configuration -file. Therefore, it will not wrap the WSGI application present in the ``env`` -dictionary as ``app`` using WebError's ``evalerror`` middleware. The ``app`` -object present in the ``env`` dictionary returned by -:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` -instead. +Assuming that you have a route configured in your application like so: -By default, Pyramid will generate a request object in the ``env`` dictionary -anchored at the root path (``/``). You can alternately supply your own -:class:`pyramid.request.Request` instance to the -:func:`pyramid.paster.bootstrap` function, to set up request parameters -beforehand: +.. code-block:: python + + config.add_route('verify', '/verify/{code}') + +You need to inform the Pyramid environment that the WSGI application is +handling requests from a certain base. For example, we want to mount our +application at `example.com/prefix` and the generated URLs should use HTTPS. +This can be done by mutating the request object: .. code-block:: python - from pyramid.request import Request - request = Request.blank('/another/url') from pyramid.paster import bootstrap - env = bootstrap('/path/to/my/development.ini#another', request=request) - print env['request'].path_info # will print '/another/url' + env = bootstrap('/path/to/my/development.ini#another') + env['request'].host = 'example.com' + env['request'].scheme = 'https' + env['request'].script_name = '/prefix' + print env['request'].application_url + # will print 'https://example.com/prefix/another/url' + +Now you can readily use Pyramid's APIs for generating URLs: + +.. code-block:: python + + route_url('verify', env['request'], code='1337') + # will return 'https://example.com/prefix/verify/1337' + +Cleanup +~~~~~~~ When your scripting logic finishes, it's good manners (but not required) to call the ``closer`` callback: @@ -436,4 +447,3 @@ call the ``closer`` callback: env['closer']() - -- cgit v1.2.3 From f06ce6f0dad47bd9b804b17b04035252fdd7868e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 20:05:35 -0400 Subject: add note about removing make_request --- CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index e8fce9c8d..fd10a7219 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,13 @@ Documentation - Added a section entitled "Writing a Script" to the "Command-Line Pyramid" chapter. +Backwards Incompatibilities +--------------------------- + +- We added the ``pyramid.scripting.make_request`` API too hastily in 1.1b3. + It has been removed. Sorry for any inconvenience. Use the + ``pyramid.request.Request.blank`` API instead. + Bug Fixes --------- -- cgit v1.2.3 From 95df4223bfd0306a9296214c30ca14dccdd8c6c4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 20:07:52 -0400 Subject: mention paster pshell using prepare --- CHANGES.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index fd10a7219..895713767 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,6 +14,15 @@ Backwards Incompatibilities It has been removed. Sorry for any inconvenience. Use the ``pyramid.request.Request.blank`` API instead. +Features +-------- + +- The ``paster pshell`` command now under the hood uses + ``pyramid.scripting.prepare``, which makes it possible to supply an + ``.ini`` file without naming the "right" section in the file that points at + the actual Pyramid application. Instead, you can generally just run + ``paster pshell development.ini`` and it will do mostly the right thing. + Bug Fixes --------- -- cgit v1.2.3 From 9c5b83710cd86ef4ddeae49a37518d869e504308 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 20:09:05 -0400 Subject: mention paster pshell using bootstrap --- CHANGES.txt | 2 +- docs/whatsnew-1.1.rst | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 895713767..5a709b332 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,7 +18,7 @@ Features -------- - The ``paster pshell`` command now under the hood uses - ``pyramid.scripting.prepare``, which makes it possible to supply an + ``pyramid.paster.bootstrap``, which makes it possible to supply an ``.ini`` file without naming the "right" section in the file that points at the actual Pyramid application. Instead, you can generally just run ``paster pshell development.ini`` and it will do mostly the right thing. diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 48be2190f..40deb55e4 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -184,6 +184,12 @@ Minor Feature Additions - ``paster pshell`` now offers more built-in global variables by default (including ``app`` and ``settings``). See :ref:`interactive_shell`. +- The ``paster pshell`` command now under the hood uses + :func:`pyramid.paster.bootstrap`, which makes it possible to supply an + ``.ini`` file without naming the "right" section in the file that points at + the actual Pyramid application. Instead, you can generally just run + ``paster pshell development.ini`` and it will do mostly the right thing. + - It is now possible to add a ``[pshell]`` section to your application's .ini configuration file, which influences the global names available to a pshell session. See :ref:`extending_pshell`. -- cgit v1.2.3 From e0c5c8eb14ccd916fd49199a96f8e9095e0fb9f0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 20:12:11 -0400 Subject: garden --- TODO.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TODO.txt b/TODO.txt index 56fd89584..8b49a30b6 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,6 +1,11 @@ Pyramid TODOs ============= +Must-Have +--------- + +- Change paster pviews and paster proutes to use bootstrap. + Should-Have ----------- -- cgit v1.2.3 From 7141f0dfc4b77817017a530cfd2dbb62b4836332 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 20:25:49 -0400 Subject: mention manual logging config --- docs/narr/commandline.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 30e678f07..890f42c42 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -447,3 +447,15 @@ call the ``closer`` callback: env['closer']() +Setting Up Logging +~~~~~~~~~~~~~~~~~~ + +By default, :func:`pyramid.paster.bootstrap` does not configure logging +parameters present in the configuration file. If you'd like to configure +logging based on ``[logger]`` and related sections in the configuration file, +use the following command: + +.. code-block:: python + + import logging + logging.fileConfig('/path/to/my/development.ini') -- cgit v1.2.3 From af056046970db9b1d3732f4c5978fcb3fb863d1f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 21:23:07 -0400 Subject: - Change paster pviews and paster proutes to use bootstrap. --- CHANGES.txt | 12 +-- docs/narr/commandline.rst | 125 +++++++++++++++-------------- docs/whatsnew-1.1.rst | 14 ++-- pyramid/paster.py | 23 ++---- pyramid/tests/test_paster.py | 187 +++++++++++++++---------------------------- 5 files changed, 150 insertions(+), 211 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5a709b332..9edeb63c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,11 +17,13 @@ Backwards Incompatibilities Features -------- -- The ``paster pshell`` command now under the hood uses - ``pyramid.paster.bootstrap``, which makes it possible to supply an - ``.ini`` file without naming the "right" section in the file that points at - the actual Pyramid application. Instead, you can generally just run - ``paster pshell development.ini`` and it will do mostly the right thing. +- The ``paster pshell``, ``paster pviews``, and ``paster proutes`` commands + each now under the hood uses ``pyramid.paster.bootstrap``, which makes it + possible to supply an ``.ini`` file without naming the "right" section in + the file that points at the actual Pyramid application. Instead, you can + generally just run ``paster {pshell|proutes|pviews} development.ini`` and + it will do mostly the right thing. + Bug Fixes --------- diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 890f42c42..b52faed20 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -20,18 +20,19 @@ For a big application with several views, it can be hard to keep the view configuration details in your head, even if you defined all the views yourself. You can use the ``paster pviews`` command in a terminal window to print a summary of matching routes and views for a given URL in your -application. The ``paster pviews`` command accepts two arguments. The -first argument to ``pviews`` is the path to your application's ``.ini`` file -and section name inside the ``.ini`` file which points to your application. -This should be of the format ``config_file#section_name``. The second argument -is the URL to test for matching views. +application. The ``paster pviews`` command accepts two arguments. The first +argument to ``pviews`` is the path to your application's ``.ini`` file and +section name inside the ``.ini`` file which points to your application. This +should be of the format ``config_file#section_name``. The second argument is +the URL to test for matching views. The ``section_name`` may be omitted; if +it is, it's considered to be ``main``. Here is an example for a simple view configuration using :term:`traversal`: .. code-block:: text :linenos: - $ ../bin/paster pviews development.ini tutorial /FrontPage + $ ../bin/paster pviews development.ini#tutorial /FrontPage URL = /FrontPage @@ -125,9 +126,8 @@ application runs "for real". To do so, use the ``paster pshell`` command. The argument to ``pshell`` follows the format ``config_file#section_name`` where ``config_file`` is the path to your application's ``.ini`` file and ``section_name`` is the ``app`` section name inside the ``.ini`` file which -points to *your application* as opposed to any other section within the -``.ini`` file. For example, if your application ``.ini`` file might have a -``[app:MyProject]`` section that looks like so: +points to your application. For example, if your application ``.ini`` file +might have a ``[app:MyProject]`` section that looks like so: .. code-block:: ini :linenos: @@ -145,58 +145,41 @@ name ``MyProject`` as a section name: .. code-block:: text - [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini#MyProject - Python 2.4.5 (#1, Aug 29 2008, 12:27:37) - [GCC 4.0.1 (Apple Inc. build 5465)] on darwin - - Default Variables: - app The WSGI Application - root The root of the default resource tree. - registry The Pyramid registry object. - settings The Pyramid settings object. - - >>> root - - >>> registry - - >>> settings['debug_notfound'] - False - >>> from myproject.views import my_view - >>> from pyramid.request import Request - >>> r = Request.blank('/') - >>> my_view(r) - {'project': 'myproject'} + chrism@thinko env26]$ bin/paster pshell starter/development.ini#MyProject + Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) + [GCC 4.4.3] on linux2 + Type "help" for more information. + + Environment: + app The WSGI application. + registry Active Pyramid registry. + request Active request object. + root Root of the default resource tree. + root_factory Default root factory used to create `root`. + >>> root + + >>> registry + + >>> registry.settings['debug_notfound'] + False + >>> from myproject.views import my_view + >>> from pyramid.request import Request + >>> r = Request.blank('/') + >>> my_view(r) + {'project': 'myproject'} The WSGI application that is loaded will be available in the shell as the -``app`` global. Also, if the application that is loaded is the -:app:`Pyramid` app with no surrounding middleware, the ``root`` object -returned by the default :term:`root factory`, ``registry``, and ``settings`` -will be available. +``app`` global. Also, if the application that is loaded is the :app:`Pyramid` +app with no surrounding middleware, the ``root`` object returned by the +default :term:`root factory`, ``registry``, and ``request`` will be +available. -The interactive shell will not be able to load some of the globals like -``root``, ``registry`` and ``settings`` if the section name specified when -loading ``pshell`` is not referencing your :app:`Pyramid` application directly. -For example, if you have the following ``.ini`` file content: +You can also simply rely on the ``main`` default section name by omitting any +hash after the filename: -.. code-block:: ini - :linenos: - - [app:MyProject] - use = egg:MyProject - reload_templates = true - debug_authorization = false - debug_notfound = false - debug_templates = true - default_locale_name = en - - [pipeline:main] - pipeline = - egg:WebError#evalerror - MyProject +.. code-block:: text -Use ``MyProject`` instead of ``main`` as the section name argument to -``pshell`` against the above ``.ini`` file (e.g. ``paster pshell -development.ini#MyProject``). + chrism@thinko env26]$ bin/paster pshell starter/development.ini Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). @@ -224,8 +207,27 @@ Here, we'll assume your model is stored in the ``myapp.models`` package. t = transaction When this INI file is loaded, the extra variables ``m``, ``session`` and -``t`` will be available for use immediately. This happens regardless of -whether the ``registry`` and other special variables are loaded. +``t`` will be available for use immediately. For example: + +.. code-block:: text + + chrism@thinko env26]$ bin/paster pshell starter/development.ini + Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) + [GCC 4.4.3] on linux2 + Type "help" for more information. + + Environment: + app The WSGI application. + registry Active Pyramid registry. + request Active request object. + root Root of the default resource tree. + root_factory Default root factory used to create `root`. + + Custom Variables: + m myapp.models + session myapp.models.DBSession + t transaction + >>> IPython ~~~~~~~ @@ -258,9 +260,10 @@ You can use the ``paster proutes`` command in a terminal window to print a summary of routes related to your application. Much like the ``paster pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` command accepts one argument with the format ``config_file#section_name``. -The ``config_file`` is the path to your application's ``.ini`` file, -and ``section_name`` is the ``app`` section name inside the ``.ini`` file -which points to your application. +The ``config_file`` is the path to your application's ``.ini`` file, and +``section_name`` is the ``app`` section name inside the ``.ini`` file which +points to your application. By default, the ``section_name`` is ``main`` and +can be omitted. For example: diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 40deb55e4..9e6d7b5ae 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -181,14 +181,12 @@ Minor Feature Additions user. See "The Interactive Shell" in the "Creating a Pyramid Project" narrative documentation section. -- ``paster pshell`` now offers more built-in global variables by default - (including ``app`` and ``settings``). See :ref:`interactive_shell`. - -- The ``paster pshell`` command now under the hood uses - :func:`pyramid.paster.bootstrap`, which makes it possible to supply an - ``.ini`` file without naming the "right" section in the file that points at - the actual Pyramid application. Instead, you can generally just run - ``paster pshell development.ini`` and it will do mostly the right thing. +- The ``paster pshell``, ``paster pviews``, and ``paster proutes`` commands + each now under the hood uses :func:`pyramid.paster.bootstrap`, which makes + it possible to supply an ``.ini`` file without naming the "right" section + in the file that points at the actual Pyramid application. Instead, you + can generally just run ``paster {pshell|proutes|pviews} development.ini`` + and it will do mostly the right thing. - It is now possible to add a ``[pshell]`` section to your application's .ini configuration file, which influences the global names available to a pshell diff --git a/pyramid/paster.py b/pyramid/paster.py index 444608f92..bb499ae8b 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -90,6 +90,7 @@ class PCommand(Command): group_name = 'pyramid' interact = (interact,) # for testing loadapp = (loadapp,) # for testing + bootstrap = (bootstrap,) # testing verbose = 3 def __init__(self, *arg, **kw): @@ -129,7 +130,6 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") - bootstrap = (bootstrap,) # testing ConfigParser = ConfigParser.ConfigParser # testing def pshell_file_config(self, filename): @@ -229,10 +229,6 @@ class PRoutesCommand(PCommand): $ paster proutes myapp.ini#main - .. note:: You should use a ``section_name`` that refers to the - actual ``app`` section in the config file that points at - your Pyramid app without any middleware wrapping, or this - command will almost certainly fail. """ summary = "Print all URL dispatch routes related to a Pyramid application" min_args = 1 @@ -241,9 +237,8 @@ class PRoutesCommand(PCommand): parser = Command.standard_parser(simulate=True) - def _get_mapper(self, app): + def _get_mapper(self, registry): from pyramid.config import Configurator - registry = app.registry config = Configurator(registry = registry) return config.get_routes_mapper() @@ -256,9 +251,9 @@ class PRoutesCommand(PCommand): from pyramid.interfaces import IView from zope.interface import Interface config_uri = self.args[0] - app = self.get_app(config_uri, loadapp=self.loadapp[0]) - registry = app.registry - mapper = self._get_mapper(app) + env = self.bootstrap[0](config_uri) + registry = env['registry'] + mapper = self._get_mapper(registry) if mapper is not None: routes = mapper.get_routes() fmt = '%-15s %-30s %-25s' @@ -300,10 +295,6 @@ class PViewsCommand(PCommand): $ paster proutes myapp.ini#main url - .. note:: You should use a ``section_name`` that refers to the - actual ``app`` section in the config file that points at - your Pyramid app without any middleware wrapping, or this - command will almost certainly fail. """ summary = "Print all views in an application that might match a URL" min_args = 2 @@ -505,8 +496,8 @@ class PViewsCommand(PCommand): config_uri, url = self.args if not url.startswith('/'): url = '/%s' % url - app = self.get_app(config_uri, loadapp=self.loadapp[0]) - registry = app.registry + env = self.bootstrap[0](config_uri) + registry = env['registry'] view = self._find_view(url, registry) self.out('') self.out("URL = %s" % url) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 4a099bbdb..91677fff5 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -5,9 +5,8 @@ class TestPShellCommand(unittest.TestCase): from pyramid.paster import PShellCommand return PShellCommand - def _makeOne(self, patch_interact=True, patch_bootstrap=True, - patch_config=True, patch_args=True, patch_options=True): - cmd = self._getTargetClass()('pshell') + def _patch(self, cmd, patch_interact=True, patch_bootstrap=True, + patch_config=True, patch_args=True, patch_options=True): if patch_interact: self.interact = DummyInteractor() cmd.interact = (self.interact,) @@ -25,6 +24,15 @@ class TestPShellCommand(unittest.TestCase): self.options = Options() self.options.disable_ipython = True cmd.options = self.options + def _makeOne(self, patch_interact=True, patch_bootstrap=True, + patch_config=True, patch_args=True, patch_options=True): + cmd = self._getTargetClass()('pshell') + self._patch(cmd, patch_interact=patch_interact, + patch_bootstrap=patch_bootstrap, + patch_config=patch_config, + patch_args=patch_args, + patch_options=patch_options, + ) return cmd def test_command_ipshell_is_None_ipython_enabled(self): @@ -123,7 +131,10 @@ class TestPRoutesCommand(unittest.TestCase): return PRoutesCommand def _makeOne(self): - return self._getTargetClass()('proutes') + cmd = self._getTargetClass()('proutes') + cmd.bootstrap = (DummyBootstrap(),) + cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd def test_no_routes(self): command = self._makeOne() @@ -131,10 +142,6 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - app = DummyApp() - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -144,10 +151,6 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg:None L = [] command.out = L.append - app = DummyApp() - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -159,10 +162,6 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - app = DummyApp() - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -183,11 +182,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) + command.bootstrap = (DummyBootstrap(registry=registry),) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -213,11 +208,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) + command.bootstrap = (DummyBootstrap(registry=registry),) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -246,11 +237,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp',) + command.bootstrap = (DummyBootstrap(registry=registry),) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -261,10 +248,7 @@ class TestPRoutesCommand(unittest.TestCase): from pyramid.urldispatch import RoutesMapper command = self._makeOne() registry = Registry() - class App: pass - app = App() - app.registry = registry - result = command._get_mapper(app) + result = command._get_mapper(registry) self.assertEqual(result.__class__, RoutesMapper) class TestPViewsCommand(unittest.TestCase): @@ -272,18 +256,22 @@ class TestPViewsCommand(unittest.TestCase): from pyramid.paster import PViewsCommand return PViewsCommand - def _makeOne(self): - return self._getTargetClass()('pviews') + def _makeOne(self, registry=None): + cmd = self._getTargetClass()('pviews') + cmd.bootstrap = (DummyBootstrap(registry=registry),) + cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd - def failUnless(self, condition): - # silence stupid deprecation under Python >= 2.7 - self.assertTrue(condition) + def _register_mapper(self, registry, routes): + from pyramid.interfaces import IRoutesMapper + mapper = DummyMapper(*routes) + registry.registerUtility(mapper, IRoutesMapper) def test__find_view_no_match(self): from pyramid.registry import Registry registry = Registry() self._register_mapper(registry, []) - command = self._makeOne() + command = self._makeOne(registry) result = command._find_view('/a', registry) self.assertEqual(result, None) @@ -305,7 +293,7 @@ class TestPViewsCommand(unittest.TestCase): (IViewClassifier, IRequest, root_iface), IMultiView) self._register_mapper(registry, []) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/x', registry) self.assertEqual(result, None) @@ -325,7 +313,7 @@ class TestPViewsCommand(unittest.TestCase): (IViewClassifier, IRequest, root_iface), IView, name='a') self._register_mapper(registry, []) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/a', registry) self.assertEqual(result, view1) @@ -348,7 +336,7 @@ class TestPViewsCommand(unittest.TestCase): (IViewClassifier, IRequest, root_iface), IMultiView, name='a') self._register_mapper(registry, []) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/a', registry) self.assertEqual(result, view) @@ -376,7 +364,7 @@ class TestPViewsCommand(unittest.TestCase): routes = [DummyRoute('a', '/a', factory=Factory, matchdict={}), DummyRoute('b', '/b', factory=Factory)] self._register_mapper(registry, routes) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/a', registry) self.assertEqual(result, view) @@ -406,9 +394,9 @@ class TestPViewsCommand(unittest.TestCase): routes = [DummyRoute('a', '/a', matchdict={}), DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/a', registry) - self.failUnless(IMultiView.providedBy(result)) + self.assertTrue(IMultiView.providedBy(result)) def test__find_view_route_multiview(self): from zope.interface import Interface @@ -444,12 +432,12 @@ class TestPViewsCommand(unittest.TestCase): routes = [DummyRoute('a', '/a', matchdict={}), DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) - command = self._makeOne() + command = self._makeOne(registry=registry) result = command._find_view('/a', registry) - self.failUnless(IMultiView.providedBy(result)) + self.assertTrue(IMultiView.providedBy(result)) self.assertEqual(len(result.views), 2) - self.failUnless((None, view1, None) in result.views) - self.failUnless((None, view2, None) in result.views) + self.assertTrue((None, view1, None) in result.views) + self.assertTrue((None, view2, None) in result.views) def test__find_multi_routes_all_match(self): command = self._makeOne() @@ -484,15 +472,11 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_not_found(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append command._find_view = lambda arg1, arg2: None - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -501,15 +485,11 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_not_found_url_starts_without_slash(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append command._find_view = lambda arg1, arg2: None - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', 'a') result = command.command() self.assertEqual(result, None) @@ -518,16 +498,12 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_traversal(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append view = DummyView(context='context', view_name='a') command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -538,17 +514,13 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_function_traversal(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append def view(): pass view.__request_attrs__ = {'context': 'context', 'view_name': 'a'} command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -559,17 +531,13 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_traversal_with_permission(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append view = DummyView(context='context', view_name='a') view.__permission__ = 'test' command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -581,8 +549,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_traversal_with_predicates(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass @@ -590,10 +558,6 @@ class TestPViewsCommand(unittest.TestCase): view = DummyView(context='context', view_name='a') view.__predicates__ = [predicate] command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -605,18 +569,14 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_route(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append route = DummyRoute('a', '/a', matchdict={}) view = DummyView(context='context', view_name='a', matched_route=route, subpath='') command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -632,8 +592,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_multi_view_nested(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append view1 = DummyView(context='context', view_name='a1') @@ -643,10 +603,6 @@ class TestPViewsCommand(unittest.TestCase): multiview2 = DummyMultiView(multiview1, context='context', view_name='a') command._find_view = lambda arg1, arg2: multiview2 - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -658,8 +614,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_single_view_route_with_route_predicates(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass @@ -668,10 +624,6 @@ class TestPViewsCommand(unittest.TestCase): view = DummyView(context='context', view_name='a', matched_route=route, subpath='') command._find_view = lambda arg1, arg2: view - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -688,8 +640,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_multiview(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append view = DummyView(context='context') @@ -697,10 +649,6 @@ class TestPViewsCommand(unittest.TestCase): view.__view_attr__ = 'call' multiview = DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1, arg2: multiview - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -711,8 +659,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_multiview_with_permission(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append view = DummyView(context='context') @@ -721,10 +669,6 @@ class TestPViewsCommand(unittest.TestCase): view.__permission__ = 'test' multiview = DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1, arg2: multiview - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -736,8 +680,8 @@ class TestPViewsCommand(unittest.TestCase): def test_views_command_multiview_with_predicates(self): from pyramid.registry import Registry - command = self._makeOne() registry = Registry() + command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass @@ -748,10 +692,6 @@ class TestPViewsCommand(unittest.TestCase): view.__predicates__ = [predicate] multiview = DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1, arg2: multiview - app = DummyApp() - app.registry = registry - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) @@ -761,11 +701,6 @@ class TestPViewsCommand(unittest.TestCase): self.assertEqual(L[8], ' pyramid.tests.test_paster.view.call') self.assertEqual(L[9], ' view predicates (predicate = x)') - def _register_mapper(self, registry, routes): - from pyramid.interfaces import IRoutesMapper - mapper = DummyMapper(*routes) - registry.registerUtility(mapper, IRoutesMapper) - class TestGetApp(unittest.TestCase): def _callFUT(self, config_file, section_name, loadapp): from pyramid.paster import get_app @@ -959,11 +894,21 @@ class DummyBootstrap(object): def __init__(self, app=None, registry=None, request=None, root=None, root_factory=None, closer=None): self.app = app or DummyApp() - self.registry = registry or dummy_registry - self.request = request or DummyRequest({}) - self.root = root or dummy_root - self.root_factory = root_factory or Dummy() - self.closer = closer or DummyCloser() + if registry is None: + registry = DummyRegistry() + self.registry = registry + if request is None: + request = DummyRequest({}) + self.request = request + if root is None: + root = Dummy() + self.root = root + if root_factory is None: + root_factory = Dummy() + self.root_factory = root_factory + if closer is None: + closer = DummyCloser() + self.closer = closer def __call__(self, *a, **kw): self.a = a -- cgit v1.2.3 From 3864eb227947365339bb83bfdd9925224aef7449 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 21:23:20 -0400 Subject: garden --- TODO.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index 8b49a30b6..56fd89584 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,11 +1,6 @@ Pyramid TODOs ============= -Must-Have ---------- - -- Change paster pviews and paster proutes to use bootstrap. - Should-Have ----------- -- cgit v1.2.3 From f2d5c1d8c08b437b84e8bed166f313b2b5dba990 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 21:32:27 -0400 Subject: not reusing this --- pyramid/tests/test_paster.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 91677fff5..085cda810 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -5,8 +5,9 @@ class TestPShellCommand(unittest.TestCase): from pyramid.paster import PShellCommand return PShellCommand - def _patch(self, cmd, patch_interact=True, patch_bootstrap=True, - patch_config=True, patch_args=True, patch_options=True): + def _makeOne(self, patch_interact=True, patch_bootstrap=True, + patch_config=True, patch_args=True, patch_options=True): + cmd = self._getTargetClass()('pshell') if patch_interact: self.interact = DummyInteractor() cmd.interact = (self.interact,) @@ -24,15 +25,6 @@ class TestPShellCommand(unittest.TestCase): self.options = Options() self.options.disable_ipython = True cmd.options = self.options - def _makeOne(self, patch_interact=True, patch_bootstrap=True, - patch_config=True, patch_args=True, patch_options=True): - cmd = self._getTargetClass()('pshell') - self._patch(cmd, patch_interact=patch_interact, - patch_bootstrap=patch_bootstrap, - patch_config=patch_config, - patch_args=patch_args, - patch_options=patch_options, - ) return cmd def test_command_ipshell_is_None_ipython_enabled(self): -- cgit v1.2.3 From d41c8741376124cce271b311b02cf1983cf03d59 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 21:45:37 -0400 Subject: garden --- TODO.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index 56fd89584..3a23f3b26 100644 --- a/TODO.txt +++ b/TODO.txt @@ -24,9 +24,6 @@ Nice-to-Have - Nicer Mako exceptions in WebError. -- Response.RequestClass should probably be pyramid.request.Request but this - may imply actually subclassing webob.Response - - Better "Extending" chapter. - Try to make test suite pass on IronPython. @@ -69,5 +66,3 @@ Nice-to-Have http://plope.com/pyramid_auth_design_api_postmortem, phasing out the current auth-n-auth abstractions in a backwards compatible way. -- Add doc string for BeforeRender event with more details. - -- cgit v1.2.3 From 2ce6659c9c3f0cd916cb85f43bd973a53d857a14 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 16 Jul 2011 23:11:18 -0400 Subject: ordering --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 2beec2494..9b5c24a20 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,8 +24,8 @@ Front Matter .. toctree:: :maxdepth: 1 - whatsnew-1.0 whatsnew-1.1 + whatsnew-1.0 Narrative documentation ======================= -- cgit v1.2.3 From bad27ceb1675cb761c83f2f2facf1fa0e69dc48e Mon Sep 17 00:00:00 2001 From: michr Date: Sun, 17 Jul 2011 03:01:35 -0700 Subject: fix typo in urls.py :: current_route_url --- pyramid/url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/url.py b/pyramid/url.py index 2a6fda89a..548d5e9d9 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -404,7 +404,7 @@ def current_route_url(request, *elements, **kw): ``/foo/1``. If the 'current route' has the route pattern ``/foo/{page}`` and the - current current url path is ``/foo/1``, the matchdict will be + current url path is ``/foo/1``, the matchdict will be ``{'page':'1'}``. The result of ``current_route_url(request, page='2')`` in this situation will be ``/foo/2``. -- cgit v1.2.3 From 1fac3ef6ac3d3062c719056a347778074946f3fb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 18 Jul 2011 02:53:05 -0400 Subject: move import statement --- pyramid/paster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index bb499ae8b..3aa6a5f1d 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -8,6 +8,8 @@ import zope.deprecation from paste.deploy import loadapp from paste.script.command import Command +from pyramid.interfaces import IMultiView + from pyramid.scripting import get_root from pyramid.scripting import prepare from pyramid.util import DottedNameResolver @@ -275,8 +277,6 @@ class PRoutesCommand(PCommand): self.out(fmt % (route.name, route.pattern, view_callable)) -from pyramid.interfaces import IMultiView - class PViewsCommand(PCommand): """Print, for a given URL, the views that might match. Underneath each potentially matching route, list the predicates required. Underneath -- cgit v1.2.3 From 40c643e07e1a5eeb48e86070cd0ffbd998899669 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 18 Jul 2011 02:59:50 -0400 Subject: prep for 1.1b4 --- CHANGES.txt | 5 ++--- docs/conf.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9edeb63c9..1b85d39be 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.1b4 (2011-07-18) +================== Documentation ------------- @@ -24,7 +24,6 @@ Features generally just run ``paster {pshell|proutes|pviews} development.ini`` and it will do mostly the right thing. - Bug Fixes --------- diff --git a/docs/conf.py b/docs/conf.py index 5c77e083b..597090c42 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.1b3' +version = '1.1b4' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index 3ba985769..99bd761fc 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1b3', + version='1.1b4', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From ad508e672af22d9631bcfd1164c6c3b71e1c3084 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 18 Jul 2011 03:05:39 -0400 Subject: remember to pull --- RELEASING.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASING.txt b/RELEASING.txt index bb2dab87f..c3781c19d 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -1,6 +1,8 @@ Releasing Pyramid ================= +- git pull + - Make sure all unit tests pass and statement coverage is at 100%:: $ python setup.py nosetests --with-coverage -- cgit v1.2.3 From 02fe94acabd76ccb35b10f20534b6ec46f392773 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 19 Jul 2011 04:17:17 -0400 Subject: remove process=1 option; see http://stackoverflow.com/questions/6703850/cant-get-pyramid-to-work-with-mod-wsgi --- docs/tutorials/modwsgi/index.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 6e3e4ce37..485eec169 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -102,8 +102,7 @@ commands and files. # play badly with C extensions. WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On - WSGIDaemonProcess pyramid user=chrism group=staff processes=1 \ - threads=4 \ + WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \ python-path=/Users/chrism/modwsgi/env/lib/python2.6/site-packages WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi -- cgit v1.2.3 From ead3c7e169dbac62f97cbaffc15f3c40430b70ea Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 19 Jul 2011 08:26:05 -0400 Subject: - Fixed two typos in wiki2 (SQLA + URL Dispatch) tutorial. --- CHANGES.txt | 8 ++++++++ docs/tutorials/wiki2/authorization.rst | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1b85d39be..117204e11 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,11 @@ +Next release +============ + +Documentation +------------- + +- Fixed two typos in wiki2 (SQLA + URL Dispatch) tutorial. + 1.1b4 (2011-07-18) ================== diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 76ce4b83f..df5e228fd 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -54,7 +54,7 @@ inside our ``models.py`` file. Add the following statements to your ``models.py`` file: .. literalinclude:: src/authorization/tutorial/models.py - :lines: 3-4,45-49 + :lines: 3-4,45-50 :linenos: :language: python @@ -228,7 +228,7 @@ We'll then change the return value of these views to pass the `resulting .. code-block:: python :linenos: - return dict(page = context, + return dict(page = page, content = content, logged_in = logged_in, edit_url = edit_url) -- cgit v1.2.3 From 9dfd3436711d6ef5f4a759f43acca859a9391c26 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 04:48:38 -0400 Subject: use env26 --- docs/make_book | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make_book b/docs/make_book index dc8381845..22883ac4a 100755 --- a/docs/make_book +++ b/docs/make_book @@ -1,4 +1,4 @@ #!/bin/sh -make clean latex SPHINXBUILD=../bookenv/bin/sphinx-build BOOK=1 +make clean latex SPHINXBUILD=../env26/bin/sphinx-build BOOK=1 cd _build/latex && make all-pdf -- cgit v1.2.3 From 6ce1e0cf1a141767ee0aca70786c15dd993347c5 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 06:10:38 -0400 Subject: add more index markers --- docs/narr/advconfig.rst | 9 ++++++++- docs/narr/assets.rst | 5 +++++ docs/narr/commandline.rst | 10 ++++++++++ docs/narr/environment.rst | 6 ++++++ docs/narr/firstapp.rst | 3 +++ docs/narr/hooks.rst | 3 ++- docs/narr/hybrid.rst | 13 +++++++++++++ docs/narr/i18n.rst | 23 +++++++++++++++++++++++ docs/narr/install.rst | 15 ++++++++++++++- docs/narr/introduction.rst | 3 +-- docs/narr/muchadoabouttraversal.rst | 15 ++++++++++++--- docs/narr/project.rst | 20 +++++++++++++++++++- docs/narr/renderers.rst | 15 ++++++++++++--- docs/narr/resources.rst | 18 ++++++++++++++++++ docs/narr/router.rst | 1 + docs/narr/security.rst | 4 ++++ docs/narr/sessions.rst | 24 +++++++++++++++++++++++- docs/narr/startup.rst | 4 ++++ docs/narr/templates.rst | 14 +++++++++++++- docs/narr/urldispatch.rst | 24 ++++++++++++++++++++++++ docs/narr/vhosting.rst | 3 +++ docs/narr/viewconfig.rst | 15 ++++++++++++++- docs/narr/views.rst | 6 ++++++ docs/narr/webob.rst | 5 ++++- 24 files changed, 242 insertions(+), 16 deletions(-) diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 3bd9c2a4e..8040a465f 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -14,7 +14,7 @@ you to ignore relative configuration statement ordering in some circumstances. .. index:: - single: imperative configuration + pair: configuration; conflict detection .. _conflict_detection: @@ -299,6 +299,9 @@ These are the methods of the configurator which provide conflict detection: provides conflict detection, because it's implemented in terms of the conflict-aware ``add_route`` and ``add_view`` methods. +.. index:: + pair: configuration; including from external sources + .. _including_configuration: Including Configuration from External Sources @@ -397,6 +400,10 @@ constraints: the routes they imply require relative ordering. Such ordering constraints are not absolved by two-phase configuration. Routes are still added in configuration execution order. +.. index:: + single: add_directive + pair: configurator; adding directives + .. _add_directive: Adding Methods to the Configurator via ``add_directive`` diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index f35f6dd7d..e609a3eab 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -35,6 +35,9 @@ static assets. For example, there's a ``static`` directory which contains ``.css``, ``.js``, and ``.gif`` files. These asset files are delivered when a user visits an application URL. +.. index:: + single: asset specifications + .. _asset_specifications: Understanding Asset Specifications @@ -85,6 +88,7 @@ individual documentation. .. index:: single: add_static_view + pair: assets; serving .. _static_assets_section: @@ -186,6 +190,7 @@ discussed in more detail later in this chapter. .. index:: single: generating static asset urls single: static asset urls + pair: assets; generating urls .. _generating_static_asset_urls: diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index b52faed20..a6ba99e17 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -183,6 +183,9 @@ hash after the filename: Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). +.. index:: + pair: pshell; extending + .. _extending_pshell: Extending the Shell @@ -229,6 +232,9 @@ When this INI file is loaded, the extra variables ``m``, ``session`` and t transaction >>> +.. index:: + single: IPython + IPython ~~~~~~~ @@ -289,6 +295,10 @@ callable could be found. If no routes are configured within your application, nothing will be printed to the console when ``paster proutes`` is executed. +.. index:: + single: scripting + single: bootstrap + .. _writing_a_script: Writing a Script diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 7f8e3953d..53234cba1 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -8,9 +8,12 @@ single: debug_all single: reload_all single: debug settings + single: debug_routematch + single: prevent_http_cache single: reload settings single: default_locale_name single: environment variables + single: Mako environment settings single: ini file settings single: PasteDeploy settings @@ -419,6 +422,9 @@ around in overridden asset directories. ``reload_assets`` makes the system *very slow* when templates are in use. Never set ``reload_assets`` to ``True`` on a production system. +.. index:: + par: settings; adding custom + .. _adding_a_custom_setting: Adding A Custom Setting diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 87487b444..42711784b 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -1,3 +1,6 @@ +.. index:: + single: hello world program + .. _firstapp_chapter: Creating Your First :app:`Pyramid` Application diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index a156426ae..2a6414e1f 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -218,7 +218,7 @@ Another (deprecated) mechanism which allows event subscribers more control when adding renderer global values exists in :ref:`adding_renderer_globals`. .. index:: - single: renderer globals + single: adding renderer globals .. _adding_renderer_globals: @@ -528,6 +528,7 @@ The default context URL generator is available for perusal as the class .. index:: single: IResponse + single: special view responses .. _using_iresponse: diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index a0a6a108c..ab1bf20bb 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -79,6 +79,9 @@ Typically, an application that uses traversal exclusively won't perform any calls to :meth:`pyramid.config.Configurator.add_route` in its startup code. +.. index:: + single: hybrid applications + Hybrid Applications ------------------- @@ -177,6 +180,9 @@ match is straightforward. When a route is matched: root factory were explained previously within :ref:`the_resource_tree`. +.. index:: + pair: hybrid applications; *traverse route pattern + .. _using_traverse_in_a_route_pattern: Using ``*traverse`` In a Route Pattern @@ -400,6 +406,9 @@ Traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). +.. index:: + pair: hybrid applications; global views + Making Global Views Match +++++++++++++++++++++++++ @@ -420,6 +429,7 @@ attribute. config.add_view('myproject.views.bazbuz', name='bazbuz') .. index:: + pair: hybrid applications; *subpath single: route subpath single: subpath (route) @@ -454,6 +464,9 @@ commonly in route declarations that look like this: :class:`pyramid.static.static_view`. This effectively tells the static helper to traverse everything in the subpath as a filename. +.. index:: + pair: hybrid applications; corner cases + Corner Cases ------------ diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index c21a19b5b..924fb047a 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -353,6 +353,9 @@ The message catalog ``.pot`` template will end up in: ``myapplication/locale/myapplication.pot``. +.. index:: + single: translation domains + Translation Domains +++++++++++++++++++ @@ -494,6 +497,8 @@ translations will be available to :app:`Pyramid`. .. index:: single: localizer single: get_localizer + single: translation + single: pluralization Using a Localizer ----------------- @@ -744,6 +749,9 @@ this support out of the box and may need special code to do an equivalent. For those, you can always use the more manual translation facility described in :ref:`performing_a_translation`. +.. index:: + single: Mako i18n + Mako Pyramid I18N Support ------------------------- @@ -798,6 +806,9 @@ If this setting is supplied within the :app:`Pyramid` application settings = get_current_registry().settings default_locale_name = settings['default_locale_name'] +.. index:: + single: detecting langauges + "Detecting" Available Languages ------------------------------- @@ -857,6 +868,9 @@ languages" configuration scheme as necessary. pair: locale; negotiator single: translation directory +.. index:: + pair: activating; translation + .. _activating_translation: Activating Translation @@ -869,6 +883,9 @@ To turn translation on, you must: - ensure that your application sets the :term:`locale name` correctly. +.. index:: + pair: translation directory; adding + .. _adding_a_translation_directory: Adding a Translation Directory @@ -906,6 +923,9 @@ will be merged into translations from a message catalog added earlier if both translation directories contain translations for the same locale and :term:`translation domain`. +.. index:: + pair: setting; locale + Setting the Locale ~~~~~~~~~~~~~~~~~~ @@ -936,6 +956,9 @@ things before any translations need to be performed: function into that application as required. See :ref:`custom_locale_negotiator`. +.. index:: + single: locale negotiator + .. _locale_negotiators: Locale Negotiators diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 5b9e22182..71988469a 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -34,6 +34,9 @@ you can either install Python using your operating system's package manager *or* you can install Python from source fairly easily on any UNIX system that has development tools. +.. index:: + pair: install; Python (from package, UNIX) + Package Manager Method ++++++++++++++++++++++ @@ -52,6 +55,9 @@ command: Once these steps are performed, the Python interpreter will usually be invokable via ``python2.6`` from a shell prompt. +.. index:: + pair: install; Python (from source, UNIX) + Source Compile Method +++++++++++++++++++++ @@ -95,6 +101,9 @@ Once these steps are performed, the Python interpreter will be invokable via ``$HOME/opt/Python-2.6.4/bin/python`` from a shell prompt. +.. index:: + pair: install; Python (from package, Windows) + If You Don't Yet Have A Python Interpreter (Windows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -180,7 +189,7 @@ the script. To remediate this, you may need to do: $ sudo python ez_setup.py .. index:: - single: virtualenv + pair: install; virtualenv Installing the ``virtualenv`` Package ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -204,6 +213,7 @@ to install it as your system's administrative user. For example: .. index:: single: virtualenv + pair: Python; virtual environment Creating the Virtual Python Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -312,6 +322,9 @@ Installing :app:`Pyramid` on Google App Engine :ref:`appengine_tutorial` documents the steps required to install a :app:`Pyramid` application on Google App Engine. +.. index:: + single: installing on Jython + Installing :app:`Pyramid` on Jython -------------------------------------- diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index d26f1b8bf..6cc5a87e5 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -67,8 +67,7 @@ Openness open source license `_. .. index:: - single: Pylons - single: repoze namespace package + single: Pylons Project What Is The Pylons Project? --------------------------- diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index a4709ef18..6ad33c1ce 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -39,6 +39,9 @@ web developer so you know when you might want to use them. :term:`Traversal` is actually a straightforward metaphor easily comprehended by anyone who's ever used a run-of-the-mill file system with folders and files. +.. index:: + single: URL dispatch + URL Dispatch ------------ @@ -100,12 +103,12 @@ from this process to the client as the final result. The server configuration specified which files would trigger some dynamic code, with the default case being to just serve the static file. +.. index:: + single: traversal + Traversal (aka Resource Location) --------------------------------- -.. index:: - single: traversal overview - Believe it or not, if you understand how serving files from a file system works, you understand traversal. And if you understand that a server might do something different based on what type of file a given request specifies, @@ -141,6 +144,9 @@ generated anywhere along the way, :app:`Pyramid` will return 404. (This isn't precisely true, as you'll see when we learn about view lookup below, but the basic idea holds.) +.. index:: + single: resource + What Is a "Resource"? --------------------- @@ -194,6 +200,9 @@ system. Traversal is in fact a superset of file system lookup. .. note:: See the chapter entitled :ref:`resources_chapter` for a more technical overview of resources. +.. index:: + single: view lookup + View Lookup ----------- diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 4b08d09f6..cdf57729d 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -249,7 +249,6 @@ create`` -generated project. Within a project generated by the single: paster serve single: reload single: startup - single: mod_wsgi Running The Project Application ------------------------------- @@ -295,6 +294,10 @@ For more detailed information about the startup process, see configuration file settings that influence startup and runtime behavior, see :ref:`environment_chapter`. +.. index:: + single: mod_wsgi + single: WSGI + Viewing the Application ----------------------- @@ -533,6 +536,9 @@ implementations. to your application's ``main`` function as ``global_config`` (see the reference to the ``main`` function in :ref:`init_py`). +.. index:: + single: production.ini + ``production.ini`` ~~~~~~~~~~~~~~~~~~~ @@ -658,6 +664,9 @@ who want to use your application. setuptools add-on such as ``setuptools-git`` or ``setuptools-hg`` for this behavior to work properly. +.. index:: + single: setup.cfg + ``setup.cfg`` ~~~~~~~~~~~~~ @@ -753,6 +762,9 @@ also informs Python that the directory which contains it is a *package*. Line 12 returns a :term:`WSGI` application to the caller of the function (Paste). +.. index:: + single: views.py + ``views.py`` ~~~~~~~~~~~~ @@ -823,6 +835,9 @@ about which sort of data storage you'll want to use, so the sample application uses an instance of :class:`myproject.resources.Root` to represent the root. +.. index:: + single: static directory + ``static`` ~~~~~~~~~~ @@ -863,6 +878,9 @@ example. See :ref:`testing_chapter` for more information about writing :app:`Pyramid` unit tests. +.. index:: + pair: modifying; package structure + .. _modifying_package_structure: Modifying Package Structure diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index f329a7af9..572d5855e 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -228,6 +228,9 @@ Views which use the JSON renderer can vary non-body response attributes by using the api of the ``request.response`` attribute. See :ref:`request_response_attr`. +.. index:: + pair: renderer; JSONP + .. _jsonp_renderer: JSONP Renderer @@ -522,9 +525,6 @@ people with older code bases. returning various values in the ``response_headerlist``, this is purely a convenience. -.. index:: - single: renderer (adding) - .. _adding_and_overriding_renderers: Adding and Changing Renderers @@ -550,6 +550,9 @@ The first argument is the renderer name. The second argument is a reference to an implementation of a :term:`renderer factory` or a :term:`dotted Python name` referring to such an object. +.. index:: + pair: renderer; adding + .. _adding_a_renderer: Adding a New Renderer @@ -676,6 +679,9 @@ ending with ``.jinja2`` in its ``renderer`` value. The ``name`` passed to the ``MyJinja2Renderer`` constructor will be the full value that was set as ``renderer=`` in the view configuration. +.. index:: + pair: renderer; changing + Changing an Existing Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -715,6 +721,9 @@ the ``name`` attribute to the renderer tag: config.add_renderer(None, 'mypackage.json_renderer_factory') +.. index:: + pair: renderer; overriding at runtime + Overriding A Renderer At Runtime -------------------------------- diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index fa8ccc549..0e0d00020 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -286,6 +286,9 @@ The shortcut method of the :term:`request` named For more information about generating resource URLs, see the documentation for :func:`pyramid.url.resource_url`. +.. index:: + pair: resource URL generation; overriding + .. _overriding_resource_url_generation: Overriding Resource URL Generation @@ -333,6 +336,9 @@ qualified, should end in a slash, and should not contain any query string or anchor elements (only path elements) to work best with :func:`~pyramid.url.resource_url`. +.. index:: + single: resource path generation + Generating the Path To a Resource --------------------------------- @@ -368,6 +374,9 @@ The resource passed in must be :term:`location`-aware. The presence or absence of a :term:`virtual root` has no impact on the behavior of :func:`~pyramid.traversal.resource_path`. +.. index:: + pair: resource; finding by path + Finding a Resource by Path -------------------------- @@ -404,6 +413,9 @@ tree does not exist), a :exc:`KeyError` will be raised. See the :func:`pyramid.traversal.find_resource` documentation for more information about resolving a path to a resource. +.. index:: + pair: resource; lineage + Obtaining the Lineage of a Resource ----------------------------------- @@ -471,6 +483,9 @@ parent (or one of its parent's parents, etc.) is an ancestor. See :func:`pyramid.location.inside` for more information. +.. index:: + pair: resource; finding root + Finding the Root Resource ------------------------- @@ -617,6 +632,9 @@ directly provided by an instance instead of overwriting them like For more information about how resource interfaces can be used by view configuration, see :ref:`using_resource_interfaces`. +.. index:: + pair: resource; finding by interface or class + Finding a Resource With a Class or Interface in Lineage ------------------------------------------------------- diff --git a/docs/narr/router.rst b/docs/narr/router.rst index 0812f7ec7..d08261b17 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -2,6 +2,7 @@ single: request processing single: request single: router + single: request lifecycle .. _router_chapter: diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 322e905f6..65f3d7cf0 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -168,6 +168,9 @@ normal application operations, the requesting user will need to possess the to invoke the ``blog_entry_add_view`` view. If he does not, the :term:`Forbidden view` will be invoked. +.. index:: + pair: permission; default + .. _setting_a_default_permission: Setting a Default Permission @@ -212,6 +215,7 @@ When a default permission is registered: .. index:: single: ACL single: access control list + pair: resource; ACL .. _assigning_acls: diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 365ee395b..6ff9e3dea 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -15,6 +15,9 @@ implementations :app:`Pyramid` provides out of the box, how to store and retrieve data from sessions, and two session-specific features: flash messages, and cross-site request forgery attack prevention. +.. index:: + single: session factory (default) + .. _using_the_default_session_factory: Using The Default Session Factory @@ -65,6 +68,9 @@ application by using the ``session_factory`` argument to the the server) for anything but the most basic of applications where "session security doesn't matter". +.. index:: + single: session object + Using a Session Object ---------------------- @@ -137,6 +143,7 @@ Some gotchas: .. index:: single: pyramid_beaker single: Beaker + single: session factory (alternates) .. _using_alternate_session_factories: @@ -153,7 +160,7 @@ based sessions, and encrypted cookie-based sessions. See ``pyramid_beaker``. .. index:: - single: session factory + single: session factory (custom) Creating Your Own Session Factory --------------------------------- @@ -184,6 +191,9 @@ log messages for single-time display without having direct access to an HTML template. The user interface consists of a number of methods of the :term:`session` object. +.. index:: + single: session.flash + Using the ``session.flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -222,6 +232,9 @@ The ``allow_duplicate`` argument defaults to ``True``. If this is ``False``, and you attempt to add a message value which is already present in the queue, it will not be added. +.. index:: + single: session.pop_flash + Using the ``session.pop_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -255,6 +268,9 @@ been popped. >>> request.session.pop_flash() [] +.. index:: + single: session.peek_flash + Using the ``session.peek_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -298,6 +314,9 @@ post. To use CSRF token support, you must enable a :term:`session factory` as described in :ref:`using_the_default_session_factory` or :ref:`using_alternate_session_factories`. +.. index:: + single: session.get_csrf_token + Using the ``session.get_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -330,6 +349,9 @@ input field named ``csrf_token``: if token != request.POST['csrf_token']: raise ValueError('CSRF token did not match') +.. index:: + single: session.new_csrf_token + Using the ``session.new_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 788896de9..8661c8f6a 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -140,6 +140,10 @@ Here's a high-level time-ordered overview of what happens when you press The server serves the application, and the application is running, waiting to receive requests. +.. index:: + pair: settings; deployment + single: custom settings + .. _deployment_settings: Deployment Settings diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 150b173e3..7c575b9bf 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -241,6 +241,9 @@ of :func:`~pyramid.renderers.render` (a string): single: renderer (template) +.. index:: + pair: renderer; system values + .. _renderer_system_values: System Values Used During Rendering @@ -277,6 +280,9 @@ renderer itself, but most template renderers, including Chameleon and Mako renderers, make these names available as top-level template variables. +.. index:: + pair: renderer; templates + .. _templates_used_as_renderers: Templates Used as Renderers via Configuration @@ -426,7 +432,7 @@ See also :ref:`built_in_renderers` for more general information about renderers, including Chameleon ZPT renderers. .. index:: - single: sample template + single: ZPT template (sample) A Sample ZPT Template ~~~~~~~~~~~~~~~~~~~~~ @@ -596,6 +602,9 @@ Note that I always name my Chameleon ZPT template files with a ``.pt`` extension and my Chameleon text template files with a ``.txt`` extension so that these ``svn:ignore`` patterns work. +.. index:: + pair: debugging; templates + .. _debug_templates_section: Nicer Exceptions in Chameleon Templates @@ -724,6 +733,9 @@ in the ``templates`` subdirectory of the ``mypackage`` Python package. See ``mako.directories`` setting and other Mako-related settings that can be placed into the application's ``ini`` file. +.. index:: + single: Mako template (sample) + A Sample Mako Template ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index b0a7009e3..a25f47690 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -50,6 +50,9 @@ a context object). But ironically, using URL dispatch (instead of terms of "resources" entirely, because it allows you to directly map a :term:`view callable` to a route. +.. index:: + single: route configuration + Route Configuration ------------------- @@ -381,6 +384,9 @@ a separate :term:`ACL`, as documented in combine URL dispatch with :term:`traversal` as documented within :ref:`hybrid_chapter`. +.. index:: + single: route configuration arguments + Route Configuration Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -402,6 +408,9 @@ neither predicates nor view configuration information. names a ``view`` and these arguments have been deprecated as of :app:`Pyramid` 1.1. +.. index:: + single: route predicates (custom) + .. _custom_route_predicates: Custom Route Predicates @@ -534,6 +543,9 @@ that the year match argument is '2010' if and only if the route name is See also :class:`pyramid.interfaces.IRoute` for more API documentation about route objects. +.. index:: + single: route matching + Route Matching -------------- @@ -745,6 +757,9 @@ representing a :term:`SQLAlchemy` model. single: matching the root URL single: root url (matching) +.. index:: + pair: matching; root URL + Matching the Root URL --------------------- @@ -914,6 +929,9 @@ The ``notfound_view`` supplied must adhere to the two-argument view callable calling convention of ``(context, request)`` (``context`` will be the exception object). +.. index:: + single: cleaning up after request + .. _cleaning_up_after_a_request: Cleaning Up After a Request @@ -997,6 +1015,9 @@ our sample ``Article`` factory class is not very ambitious. .. note:: See :ref:`security_chapter` for more information about :app:`Pyramid` security and ACLs. +.. index:: + pair: debugging; route matching + .. _debug_routematch_section: Debugging Route Matching @@ -1032,6 +1053,9 @@ You can also use the ``paster proutes`` command to see a display of all the routes configured in your application; for more information, see :ref:`displaying_application_routes`. +.. index:: + pair: route; view callable lookup details + Route View Callable Registration and Lookup Details --------------------------------------------------- diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst index d3ff260e3..5679cc2e2 100644 --- a/docs/narr/vhosting.rst +++ b/docs/narr/vhosting.rst @@ -14,6 +14,9 @@ URL space that it does not "naturally" inhabit. a URL "prefix", as well as serving a *portion* of a :term:`traversal` based application under a root URL. +.. index:: + single: hosting an app under a prefix + Hosting an Application Under a URL Prefix ----------------------------------------- diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 54d3fc4ff..94b80a3f2 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -20,6 +20,10 @@ In earlier chapters, you have been exposed to a few simple view configuration declarations without much explanation. In this chapter we will explore the subject in detail. +.. index:: + pair: resource; mapping to view callable + pair: URL pattern; mapping to view callable + Mapping a Resource or URL Pattern to a View Callable ---------------------------------------------------- @@ -41,6 +45,9 @@ View configuration is performed in one of two ways: - by using the :meth:`pyramid.config.Configurator.add_view` method as per :ref:`mapping_views_using_imperative_config_section`. +.. index:: + single: view configuration parameters + .. _view_configuration_parameters: View Configuration Parameters @@ -475,6 +482,9 @@ form of :term:`declarative configuration`, while :meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative configuration`. However, they both do the same thing. +.. index:: + single: view_config placement + ``@view_config`` Placement ++++++++++++++++++++++++++ @@ -822,7 +832,10 @@ headers that your application code itself sets. It will only prevent caching headers that would have been set by the Pyramid HTTP caching machinery invoked as the result of the ``http_cache`` argument to view configuration. -Debugging View Configuration +.. index:: + pair: view configuration; debugging + +ebugging View Configuration ---------------------------- See :ref:`displaying_matching_views` for information about how to display diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 6acb1d28d..1c9529860 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -35,6 +35,9 @@ This chapter describes how view callables work. In the :ref:`view_config_chapter` chapter, there are details about performing view configuration, and a detailed explanation of view lookup. +.. index:: + single: view callables + View Callables -------------- @@ -586,6 +589,9 @@ callable code itself. No matter which view calling convention is used, the view code always has access to the context via ``request.context``. +.. index:: + single: Pylons-style controller dispatch + Pylons-1.0-Style "Controller" Dispatch -------------------------------------- diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 0d928e532..6e8c39523 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -243,6 +243,9 @@ tuples; all the keys are ordered, and all the values are ordered. API documentation for a multidict exists as :class:`pyramid.interfaces.IMultiDict`. +.. index:: + pair: json_body; request + .. _request_json_body: Dealing With A JSON-Encoded Request Body @@ -408,7 +411,7 @@ anything, though if you subclass :class:`pyramid.response.Response` and set ``default_content_type`` you can override this behavior. .. index:: - single: response exceptions + single: exception responses Exception Responses +++++++++++++++++++ -- cgit v1.2.3 From 8cb68208d42899b50025418812bb339f578d553f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 07:16:14 -0400 Subject: - Reordered chapters in narrative section for better new user friendliness. - Added more indexing markers to sections in documentation. --- CHANGES.txt | 4 ++ docs/index.rst | 14 ++--- docs/latexindex.rst | 15 +++--- docs/narr/firstapp.rst | 3 -- docs/narr/renderers.rst | 8 +-- docs/narr/traversal.rst | 97 +++++++++++++++++++++++++++++++++ docs/narr/urldispatch.rst | 123 ++++++++++++++++++------------------------ docs/narr/viewconfig.rst | 133 +++++++--------------------------------------- docs/narr/views.rst | 68 +++++++++--------------- 9 files changed, 217 insertions(+), 248 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 117204e11..09e6139cc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,6 +6,10 @@ Documentation - Fixed two typos in wiki2 (SQLA + URL Dispatch) tutorial. +- Reordered chapters in narrative section for better new user friendliness. + +- Added more indexing markers to sections in documentation. + 1.1b4 (2011-07-18) ================== diff --git a/docs/index.rst b/docs/index.rst index 9b5c24a20..85e733dc3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,24 +43,24 @@ Narrative documentation in chapter form explaining how to use narr/project narr/startup narr/urldispatch - narr/muchadoabouttraversal - narr/traversal narr/views narr/renderers narr/templates narr/viewconfig - narr/resources narr/assets narr/webob narr/sessions - narr/security - narr/hybrid - narr/i18n - narr/vhosting narr/events narr/environment narr/commandline + narr/i18n + narr/vhosting narr/testing + narr/resources + narr/muchadoabouttraversal + narr/traversal + narr/security + narr/hybrid narr/hooks narr/advconfig narr/extending diff --git a/docs/latexindex.rst b/docs/latexindex.rst index a4926bf30..bdd923dbc 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -32,23 +32,24 @@ Narrative Documentation narr/firstapp narr/project narr/urldispatch - narr/muchadoabouttraversal - narr/traversal narr/views narr/renderers narr/templates narr/viewconfig - narr/resources narr/assets narr/webob narr/sessions - narr/security - narr/hybrid - narr/i18n - narr/vhosting narr/events narr/environment + narr/commandline + narr/i18n + narr/vhosting narr/testing + narr/resources + narr/muchadoabouttraversal + narr/traversal + narr/security + narr/hybrid narr/hooks narr/advconfig narr/extending diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 42711784b..6c53f3de0 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -322,6 +322,3 @@ see :class:`~pyramid.config.Configurator` . For more information about :term:`view configuration`, see :ref:`view_config_chapter`. -An example of using *declarative* configuration (:term:`ZCML`) instead of -imperative configuration to create a similar "hello world" is available -within the documentation for :term:`pyramid_zcml`. diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 572d5855e..801741c43 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -3,10 +3,10 @@ Renderers ========= -A view needn't *always* return a :term:`Response` object. If a view -happens to return something which does not implement the Pyramid -Response interface, :app:`Pyramid` will attempt to use a -:term:`renderer` to construct a response. For example: +A view callable needn't *always* return a :term:`Response` object. If a view +happens to return something which does not implement the Pyramid Response +interface, :app:`Pyramid` will attempt to use a :term:`renderer` to construct +a response. For example: .. code-block:: python :linenos: diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index e1715dc25..aa36b4455 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -456,6 +456,103 @@ as the sole argument: ``request``; it is expected to return a response. -specific request attributes are also available as described in :ref:`special_request_attributes`. +.. index:: + single: resource interfaces + +.. _using_resource_interfaces: + +Using Resource Interfaces In View Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of registering your views with a ``context`` that names a Python +resource *class*, you can optionally register a view callable with a +``context`` which is an :term:`interface`. An interface can be attached +arbitrarily to any resource object. View lookup treats context interfaces +specially, and therefore the identity of a resource can be divorced from that +of the class which implements it. As a result, associating a view with an +interface can provide more flexibility for sharing a single view between two +or more different implementations of a resource type. For example, if two +resource objects of different Python class types share the same interface, +you can use the same view configuration to specify both of them as a +``context``. + +In order to make use of interfaces in your application during view dispatch, +you must create an interface and mark up your resource classes or instances +with interface declarations that refer to this interface. + +To attach an interface to a resource *class*, you define the interface and +use the :func:`zope.interface.implements` function to associate the interface +with the class. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import implements + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + implements(IHello) + +To attach an interface to a resource *instance*, you define the interface and +use the :func:`zope.interface.alsoProvides` function to associate the +interface with the instance. This function mutates the instance in such a +way that the interface is attached to it. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import alsoProvides + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + pass + + def make_hello(): + hello = Hello() + alsoProvides(hello, IHello) + return hello + +Regardless of how you associate an interface, with a resource instance, or a +resource class, the resulting code to associate that interface with a view +callable is the same. Assuming the above code that defines an ``IHello`` +interface lives in the root of your application, and its module is named +"resources.py", the interface declaration below will associate the +``mypackage.views.hello_world`` view with resources that implement, or +provide, this interface. + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + + config.add_view('mypackage.views.hello_world', name='hello.html', + context='mypackage.resources.IHello') + +Any time a resource that is determined to be the :term:`context` provides +this interface, and a view named ``hello.html`` is looked up against it as +per the URL, the ``mypackage.views.hello_world`` view callable will be +invoked. + +Note, in cases where a view is registered against a resource class, and a +view is also registered against an interface that the resource class +implements, an ambiguity arises. Views registered for the resource class take +precedence over any views registered for any interface the resource class +implements. Thus, if one view configuration names a ``context`` of both the +class type of a resource, and another view configuration names a ``context`` +of interface implemented by the resource's class, and both view +configurations are otherwise identical, the view registered for the context's +class will "win". + +For more information about defining resources with interfaces for use within +view configuration, see :ref:`resources_which_implement_interfaces`. + + References ---------- diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index a25f47690..0598cd4f2 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -6,28 +6,12 @@ URL Dispatch ============ -:term:`URL dispatch` provides a simple way to map URLs to :term:`view` -code using a simple pattern matching language. An ordered set of -patterns is checked one-by-one. If one of the patterns matches the path -information associated with a request, a particular :term:`view -callable` is invoked. - -:term:`URL dispatch` is one of two ways to perform :term:`resource location` -in :app:`Pyramid`; the other way is to use :term:`traversal`. If no route is -matched using :term:`URL dispatch`, :app:`Pyramid` falls back to -:term:`traversal` to handle the :term:`request`. - -It is the responsibility of the :term:`resource location` subsystem -(i.e., :term:`URL dispatch` or :term:`traversal`) to find the resource -object that is the :term:`context` of the :term:`request`. Once the -:term:`context` is determined, :term:`view lookup` is then responsible -for finding and invoking a :term:`view callable`. A view callable is a -specific bit of code, defined in your application, that receives the -:term:`request` and returns a :term:`response` object. - -Where appropriate, we will describe how view lookup interacts with -:term:`resource location`. The :ref:`view_config_chapter` chapter describes -the details of :term:`view lookup`. +:term:`URL dispatch` provides a simple way to map URLs to :term:`view` code +using a simple pattern matching language. An ordered set of patterns is +checked one-by-one. If one of the patterns matches the path information +associated with a request, a particular :term:`view callable` is invoked. A +view callable is a specific bit of code, defined in your application, that +receives the :term:`request` and returns a :term:`response` object. High-Level Operational Overview ------------------------------- @@ -37,18 +21,11 @@ If route configuration is present in an application, the :app:`Pyramid` matching patterns present in a *route map*. If any route pattern matches the information in the :term:`request`, -:app:`Pyramid` will invoke :term:`view lookup` using a :term:`context` -resource generated by the route match. +:app:`Pyramid` will invoke :term:`view lookup` to find a matching view. -However, if no route pattern matches the information in the :term:`request` -provided to :app:`Pyramid`, it will fail over to using :term:`traversal` to -perform resource location and view lookup. - -Technically, URL dispatch is a :term:`resource location` mechanism (it finds -a context object). But ironically, using URL dispatch (instead of -:term:`traversal`) allows you to avoid thinking about your application in -terms of "resources" entirely, because it allows you to directly map a -:term:`view callable` to a route. +If no route pattern in the route map matches the information in the +:term:`request` provided in your application, :app:`Pyramid` will fail over +to using :term:`traversal` to perform resource location and view lookup. .. index:: single: route configuration @@ -89,8 +66,8 @@ example: When a :term:`view callable` added to the configuration by way of :meth:`~pyramid.config.Configurator.add_view` bcomes associated with a route -via its ``route_name`` predicate, that view callable will always be found -and invoked when the associated route pattern matches during a request. +via its ``route_name`` predicate, that view callable will always be found and +invoked when the associated route pattern matches during a request. More commonly, you will not use any ``add_view`` statements in your project's "setup" code, instead only using ``add_route`` statements using a @@ -323,12 +300,11 @@ Route Declaration Ordering Route configuration declarations are evaluated in a specific order when a request enters the system. As a result, the order of route configuration -declarations is very important. - -The order that routes declarations are evaluated is the order in which they -are added to the application at startup time. This is unlike -:term:`traversal`, which depends on emergent behavior which happens as a -result of traversing a resource tree. +declarations is very important. The order that routes declarations are +evaluated is the order in which they are added to the application at startup +time. (This is unlike a different way of mapping URLs to code that +:app:`Pyramid` provides, named :term:`traversal`, which does not depend on +pattern ordering). For routes added via the :mod:`~pyramid.config.Configurator.add_route` method, the order that routes are evaluated is the order in which they are added to @@ -551,27 +527,30 @@ Route Matching The main purpose of route configuration is to match (or not match) the ``PATH_INFO`` present in the WSGI environment provided during a request -against a URL path pattern. +against a URL path pattern. ``PATH_INFO`` represents the path portion of the +URL that was requested. The way that :app:`Pyramid` does this is very simple. When a request enters the system, for each route configuration declaration present in the system, -:app:`Pyramid` checks the ``PATH_INFO`` against the pattern declared. - -If any route matches, the route matching process stops. The :term:`request` -is decorated with a special :term:`interface` which describes it as a "route -request", the :term:`context` resource is generated, and the context and the -resulting request are handed off to :term:`view lookup`. During view lookup, -if a :term:`view callable` associated with the matched route is found, that -view is called. +:app:`Pyramid` checks the request's ``PATH_INFO`` against the pattern +declared. This checking happens in the order that the routes were declared +via :meth:`pyramid.config.Configurator.add_route`. When a route configuration is declared, it may contain :term:`route predicate` arguments. All route predicates associated with a route declaration must be ``True`` for the route configuration to be used for a -given request. - -If any predicate in the set of :term:`route predicate` arguments provided to -a route configuration returns ``False``, that route is skipped and route -matching continues through the ordered set of routes. +given request during a check. If any predicate in the set of :term:`route +predicate` arguments provided to a route configuration returns ``False`` +during a check, that route is skipped and route matching continues through +the ordered set of routes. + +If any route matches, the route matching process stops and the :term:`view +lookup` subsystem takes over to find the most reasonable view callable for +the matched route. Most often, there's only one view that will match (a view +configured with a ``route_name`` argument matching the matched route). To +gain a better understanding of how routes and views are associated in a real +application, you can use the ``paster pviews`` command, as documented in +:ref:`displaying_matching_views`. If no route matches after all route patterns are exhausted, :app:`Pyramid` falls back to :term:`traversal` to do :term:`resource location` and @@ -1083,24 +1062,28 @@ when the route pattern is matched during a request. To do so: object is decorated with the route-specific interface. - The fact that the request is decorated with a route-specific interface - causes the view lookup machinery to always use the view callable registered - using that interface by the route configuration to service requests that - match the route pattern. - -In this way, we supply a shortcut to the developer. Under the hood, the -:term:`resource location` and :term:`view lookup` subsystems provided by -:app:`Pyramid` are still being utilized, but in a way which does not require -a developer to understand either of them in detail. It also means that we -can allow a developer to combine :term:`URL dispatch` and :term:`traversal` -in various exceptional cases as documented in :ref:`hybrid_chapter`. - -To gain a better understanding of how routes and views are associated in a -real application, you can use the ``paster pviews`` command, as documented -in :ref:`displaying_matching_views`. + causes the :term:`view lookup` machinery to always use the view callable + registered using that interface by the route configuration to service + requests that match the route pattern. + +As we can see from the above description, technically, URL dispatch doesn't +actually map a URL pattern directly to a view callable. Instead, URL +dispatch is a :term:`resource location` mechanism. A :app:`Pyramid` +:term:`resource location` subsystem (i.e., :term:`URL dispatch` or +:term:`traversal`) finds a :term:`resource` object that is the +:term:`context` of a :term:`request`. Once the :term:`context` is determined, +a separate subsystem named :term:`view lookup` is then responsible for +finding and invoking a :term:`view callable` based on information available +in the context and the request. When URL dispatch is used, the resource +location and view lookup subsystems provided by :app:`Pyramid` are still +being utilized, but in a way which does not require a developer to understand +either of them in detail. + +If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls back +to :term:`traversal` to handle the :term:`request`. References ---------- A tutorial showing how :term:`URL dispatch` can be used to create a :app:`Pyramid` application exists in :ref:`bfg_sql_wiki_tutorial`. - diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index 94b80a3f2..d776887c8 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -235,8 +235,10 @@ arguments that are supplied, the more specific, and narrower the usage of the configured view. ``name`` - The :term:`view name` required to match this view callable. Read - :ref:`traversal_chapter` to understand the concept of a view name. + The :term:`view name` required to match this view callable. A ``name`` + argument is typically only used when your application uses + :term:`traversal`. Read :ref:`traversal_chapter` to understand the concept + of a view name. If ``name`` is not supplied, the empty string is used (implying the default view). @@ -417,8 +419,7 @@ lives within a :app:`Pyramid` application module ``views.py``: from pyramid.view import view_config from pyramid.response import Response - @view_config(name='my_view', request_method='POST', context=MyResource, - permission='read') + @view_config(route_name='ok', request_method='POST', permission='read') def my_view(request): return Response('OK') @@ -429,9 +430,8 @@ configuration stanza: .. code-block:: python :linenos: - config.add_view('mypackage.views.my_view', name='my_view', - request_method='POST', context=MyResource, - permission='read') + config.add_view('mypackage.views.my_view', route_name='ok', + request_method='POST', permission='read') All arguments to ``view_config`` may be omitted. For example: @@ -499,7 +499,7 @@ If your view callable is a function, it may be used as a function decorator: from pyramid.view import view_config from pyramid.response import Response - @view_config(name='edit') + @view_config(route_name='edit') def edit(request): return Response('edited!') @@ -514,7 +514,7 @@ against a class as when they are applied against a function. For example: from pyramid.response import Response from pyramid.view import view_config - @view_config() + @view_config(route_name='hello') class MyView(object): def __init__(self, request): self.request = request @@ -539,7 +539,7 @@ decorator syntactic sugar, if you wish: def __call__(self): return Response('hello') - my_view = view_config()(MyView) + my_view = view_config(route_name='hello')(MyView) More than one :class:`~pyramid.view.view_config` decorator can be stacked on top of any number of others. Each decorator creates a separate view @@ -551,8 +551,8 @@ registration. For example: from pyramid.view import view_config from pyramid.response import Response - @view_config(name='edit') - @view_config(name='change') + @view_config(route_name='edit') + @view_config(route_name='change') def edit(request): return Response('edited!') @@ -570,7 +570,7 @@ The decorator can also be used against a method of a class: def __init__(self, request): self.request = request - @view_config(name='hello') + @view_config(route_name='hello') def amethod(self): return Response('hello') @@ -592,7 +592,7 @@ against the ``amethod`` method could be spelled equivalently as the below: from pyramid.response import Response from pyramid.view import view_config - @view_config(attr='amethod', name='hello') + @view_config(attr='amethod', route_name='hello') class MyView(object): def __init__(self, request): self.request = request @@ -624,7 +624,7 @@ this method are very similar to the arguments that you provide to the # config is assumed to be an instance of the # pyramid.config.Configurator class - config.add_view(hello_world, name='hello.html') + config.add_view(hello_world, route_name='hello') The first argument, ``view``, is required. It must either be a Python object which is the view itself or a :term:`dotted Python name` to such an object. @@ -636,102 +636,6 @@ When you use only :meth:`~pyramid.config.Configurator.add_view` to add view configurations, you don't need to issue a :term:`scan` in order for the view configuration to take effect. -.. index:: - single: resource interfaces - -.. _using_resource_interfaces: - -Using Resource Interfaces In View Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of registering your views with a ``context`` that names a Python -resource *class*, you can optionally register a view callable with a -``context`` which is an :term:`interface`. An interface can be attached -arbitrarily to any resource object. View lookup treats context interfaces -specially, and therefore the identity of a resource can be divorced from that -of the class which implements it. As a result, associating a view with an -interface can provide more flexibility for sharing a single view between two -or more different implementations of a resource type. For example, if two -resource objects of different Python class types share the same interface, -you can use the same view configuration to specify both of them as a -``context``. - -In order to make use of interfaces in your application during view dispatch, -you must create an interface and mark up your resource classes or instances -with interface declarations that refer to this interface. - -To attach an interface to a resource *class*, you define the interface and -use the :func:`zope.interface.implements` function to associate the interface -with the class. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import implements - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - implements(IHello) - -To attach an interface to a resource *instance*, you define the interface and -use the :func:`zope.interface.alsoProvides` function to associate the -interface with the instance. This function mutates the instance in such a -way that the interface is attached to it. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import alsoProvides - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - pass - - def make_hello(): - hello = Hello() - alsoProvides(hello, IHello) - return hello - -Regardless of how you associate an interface, with a resource instance, or a -resource class, the resulting code to associate that interface with a view -callable is the same. Assuming the above code that defines an ``IHello`` -interface lives in the root of your application, and its module is named -"resources.py", the interface declaration below will associate the -``mypackage.views.hello_world`` view with resources that implement, or -provide, this interface. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - - config.add_view('mypackage.views.hello_world', name='hello.html', - context='mypackage.resources.IHello') - -Any time a resource that is determined to be the :term:`context` provides -this interface, and a view named ``hello.html`` is looked up against it as -per the URL, the ``mypackage.views.hello_world`` view callable will be -invoked. - -Note, in cases where a view is registered against a resource class, and a -view is also registered against an interface that the resource class -implements, an ambiguity arises. Views registered for the resource class take -precedence over any views registered for any interface the resource class -implements. Thus, if one view configuration names a ``context`` of both the -class type of a resource, and another view configuration names a ``context`` -of interface implemented by the resource's class, and both view -configurations are otherwise identical, the view registered for the context's -class will "win". - -For more information about defining resources with interfaces for use within -view configuration, see :ref:`resources_which_implement_interfaces`. - .. index:: single: view security pair: security; view @@ -753,8 +657,9 @@ configuration using :meth:`~pyramid.config.Configurator.add_view`: # config is an instance of pyramid.config.Configurator - config.add_view('myproject.views.add_entry', name='add.html', - context='myproject.resources.IBlog', permission='add') + config.add_route('add', '/add.html', factory='mypackage.Blog') + config.add_view('myproject.views.add_entry', route_name='add', + permission='add') When an :term:`authorization policy` is enabled, this view will be protected with the ``add`` permission. The view will *not be called* if the user does @@ -835,7 +740,7 @@ invoked as the result of the ``http_cache`` argument to view configuration. .. index:: pair: view configuration; debugging -ebugging View Configuration +Debugging View Configuration ---------------------------- See :ref:`displaying_matching_views` for information about how to display diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 1c9529860..a3fd61098 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -3,10 +3,10 @@ Views ===== -One of the primary jobs of :app:`Pyramid` is to find and invoke a -:term:`view callable` when a :term:`request` reaches your application. View -callables are bits of code which do something interesting in response to a -request made to your application. +One of the primary jobs of :app:`Pyramid` is to find and invoke a :term:`view +callable` when a :term:`request` reaches your application. View callables +are bits of code which do something interesting in response to a request made +to your application. They are the "meat" of any interesting web application. .. note:: @@ -17,23 +17,10 @@ request made to your application. that implements a view *callable*, and the process of view *lookup*. -The :ref:`urldispatch_chapter`, and :ref:`traversal_chapter` chapters -describes how, using information from the :term:`request`, a -:term:`context` resource is computed. But the context resource itself -isn't very useful without an associated :term:`view callable`. A view -callable returns a response to a user, often using the context resource -to do so. - -The job of actually locating and invoking the "best" :term:`view callable` is -the job of the :term:`view lookup` subsystem. The view lookup subsystem -compares the resource supplied by :term:`resource location` and information -in the :term:`request` against :term:`view configuration` statements made by -the developer to choose the most appropriate view callable for a specific -set of circumstances. - -This chapter describes how view callables work. In the -:ref:`view_config_chapter` chapter, there are details about performing -view configuration, and a detailed explanation of view lookup. +This chapter describes how view callables should be defined. We'll have to +wait until a following chapter (entitled :ref:`view_config_chapter`) to find +out how we actually tell :app:`Pyramid` to wire up view callables to +particular URL patterns and other request circumstances. .. index:: single: view callables @@ -42,26 +29,20 @@ View Callables -------------- View callables are, at the risk of sounding obvious, callable Python -objects. Specifically, view callables can be functions, classes, or -instances that implement an ``__call__`` method (making the -instance callable). +objects. Specifically, view callables can be functions, classes, or instances +that implement an ``__call__`` method (making the instance callable). View callables must, at a minimum, accept a single argument named ``request``. This argument represents a :app:`Pyramid` :term:`Request` -object. A request object encapsulates a WSGI environment provided to -:app:`Pyramid` by the upstream :term:`WSGI` server. As you might expect, -the request object contains everything your application needs to know -about the specific HTTP request being made. +object. A request object represents a :term:`WSGI` environment provided to +:app:`Pyramid` by the upstream WSGI server. As you might expect, the request +object contains everything your application needs to know about the specific +HTTP request being made. A view callable's ultimate responsibility is to create a :mod:`Pyramid` -:term:`Response` object. This can be done by creating the response object in -the view callable code and returning it directly, as we will be doing in this -chapter. However, if a view callable does not return a response itself, it -can be configured to use a :term:`renderer` that converts its return value -into a :term:`Response` object. Using renderers is the common way that -templates are used with view callables to generate markup: see the -:ref:`renderers_chapter` chapter for details. In some cases, a response may -also be generated by raising an exception within a view callable. +:term:`Response` object. This can be done by creating a :term:`Response` +object in the view callable code and returning it directly or by raising +special kinds of exceptions from within the body of a view callable. .. index:: single: view calling convention @@ -160,13 +141,14 @@ the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response object because it inherits from :class:`~pyramid.response.Response`. For examples, see :ref:`http_exceptions` and :ref:`http_redirect`. -You can also return objects from view callables that aren't instances of (or -instances of classes which are subclasses of) -:class:`pyramid.response.Response` in various circumstances. This can be -helpful when writing tests and when attempting to share code between view -callables. See :ref:`renderers_chapter` for the common way to allow for -this. A much less common way to allow for view callables to return -non-Response objects is documented in :ref:`using_iresponse`. +.. note:: + + You can also return objects from view callables that aren't instances of + :class:`pyramid.response.Response` in various circumstances. This can be + helpful when writing tests and when attempting to share code between view + callables. See :ref:`renderers_chapter` for the common way to allow for + this. A much less common way to allow for view callables to return + non-Response objects is documented in :ref:`using_iresponse`. .. index:: single: view exceptions -- cgit v1.2.3 From be9bbff6440750e56a73f534bc09511ef5d2b8b4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 07:31:33 -0400 Subject: use less awkward language --- docs/narr/configuration.rst | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 3ecb4b06a..dacf09f18 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -6,15 +6,16 @@ Application Configuration ========================= -The way in which code is plugged in to :app:`Pyramid` for a specific -application is referred to as "configuration". Most people understand -"configuration" as coarse settings that inform the high-level operation of a -specific application deployment. For instance, it's easy to think of the -values implied by a ``.ini`` file parsed at application startup time as -"configuration". However, :app:`Pyramid` also uses the word "configuration" -to express standardized ways that code gets plugged into a deployment of the -framework itself. When you plug code into the :app:`Pyramid` framework, you -are "configuring" :app:`Pyramid` to create a particular application. +Most people already understand "configuration" as settings that influence the +operation of an application. For instance, it's easy to think of the values +in a ``.ini`` file parsed at application startup time as "configuration". +However, if you're reasonably open-minded, it's easy to think of *code* as +configuration too. Since Pyramid, like most other web application platforms, +is a *framework*, it calls into code that you write (as opposed to a +*library*, which is code that exists purely for your to call). The act of +plugging application code that you've written into :app:`Pyramid` is also +referred to within this documentation as "configuration"; you are configuring +:app:`Pyramid` to call the code that makes up your application. There are two ways to configure a :app:`Pyramid` application: :term:`imperative configuration` and :term:`declarative configuration`. Both @@ -144,3 +145,10 @@ In the example above, the scanner translates the arguments to config.add_view(hello) +Summary +------- + +There are two ways to configure a :app:`Pyramid` application: declaratively +and imperatively. You can choose the mode you're most comfortable with; both +are completely equivalent. Examples in this documentation will use both +modes interchangeably. -- cgit v1.2.3 From 87a689d71191a175ee20e52701a9e802548f91a6 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 07:32:01 -0400 Subject: typo --- docs/narr/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index dacf09f18..597d48b09 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -12,7 +12,7 @@ in a ``.ini`` file parsed at application startup time as "configuration". However, if you're reasonably open-minded, it's easy to think of *code* as configuration too. Since Pyramid, like most other web application platforms, is a *framework*, it calls into code that you write (as opposed to a -*library*, which is code that exists purely for your to call). The act of +*library*, which is code that exists purely for you to call). The act of plugging application code that you've written into :app:`Pyramid` is also referred to within this documentation as "configuration"; you are configuring :app:`Pyramid` to call the code that makes up your application. -- cgit v1.2.3 From 2d751a2b0bf06d303a4313fd922a93451d380d38 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 12:34:56 -0400 Subject: pin coverage for nosexcover+tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index e404b29f4..40711a5f2 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ deps = repoze.sphinx.autointerface virtualenv nose - coverage + coverage==3.4 nosexcover # we separate coverage into its own testenv because a) "last run wins" wrt -- cgit v1.2.3 From 0bac345c6ff595302e765dfce9bd91e198d61038 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 12:39:35 -0400 Subject: name noncpython versions we work with --- docs/narr/install.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 71988469a..f543753ce 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -20,7 +20,8 @@ run :app:`Pyramid`. :app:`Pyramid` is known to run on all popular Unix-like systems such as Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also -known to run on Google's App Engine, :term:`PyPy`, and :term:`Jython`. +known to run on Google's App Engine, :term:`PyPy` (1.5), and :term:`Jython` +(2.5.2). :app:`Pyramid` installation does not require the compilation of any C code, so you need only a Python interpreter that meets the -- cgit v1.2.3 From 5adf4c099905cb41f68b38d6443bd2fe62c76889 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 20 Jul 2011 19:03:26 -0400 Subject: add description of keys and values --- docs/narr/commandline.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index a6ba99e17..f4cf951ba 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -192,10 +192,11 @@ Extending the Shell ~~~~~~~~~~~~~~~~~~~ It is sometimes convenient when using the interactive shell often to have -some variables significant to your application already loaded as globals -when you start the ``pshell``. To facilitate this, ``pshell`` will look -for a special ``[pshell]`` section in your INI file and expose the subsequent -key/value pairs to the shell. +some variables significant to your application already loaded as globals when +you start the ``pshell``. To facilitate this, ``pshell`` will look for a +special ``[pshell]`` section in your INI file and expose the subsequent +key/value pairs to the shell. Each key is a variable name that will be +global within the pshell session; each value is a :term:`dotted Python name`. For example, you want to expose your model to the shell, along with the database session so that you can mutate the model on an actual database. -- cgit v1.2.3 From e7cb93e1c07fb59a523ff8e79732787a3f4ae213 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 02:19:16 -0400 Subject: - Remove ``compat`` code that served only the purpose of providing backwards compatibility with Python 2.4. --- CHANGES.txt | 6 ++ LICENSE.txt | 53 --------------- pyramid/compat.py | 8 +++ pyramid/compat/__init__.py | 157 ------------------------------------------- pyramid/config.py | 4 +- pyramid/tests/test_compat.py | 9 --- pyramid/tests/test_config.py | 4 +- pyramid/urldispatch.py | 1 - pyramid/wsgi.py | 2 +- 9 files changed, 18 insertions(+), 226 deletions(-) create mode 100644 pyramid/compat.py delete mode 100644 pyramid/compat/__init__.py delete mode 100644 pyramid/tests/test_compat.py diff --git a/CHANGES.txt b/CHANGES.txt index 09e6139cc..609f62bd3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,12 @@ Next release ============ +Internals +--------- + +- Remove ``compat`` code that served only the purpose of providing backwards + compatibility with Python 2.4. + Documentation ------------- diff --git a/LICENSE.txt b/LICENSE.txt index 28824ee3f..f7ace1698 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -98,59 +98,6 @@ under the ZPL): SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Portions of the code in Pyramid are supplied under the Python Software -Foundation License version 2 (headers within individiual files indicate that -these portions are so licensed): - - PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 - -------------------------------------------- - - 1. This LICENSE AGREEMENT is between the Python Software Foundation - ("PSF"), and the Individual or Organization ("Licensee") accessing and - otherwise using this software ("Python") in source or binary form and - its associated documentation. - - 2. Subject to the terms and conditions of this License Agreement, PSF - hereby grants Licensee a nonexclusive, royalty-free, world-wide - license to reproduce, analyze, test, perform and/or display publicly, - prepare derivative works, distribute, and otherwise use Python - alone or in any derivative version, provided, however, that PSF's - License Agreement and PSF's notice of copyright, i.e., "Copyright (c) - 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; - All Rights Reserved" are retained in Python alone or in any derivative - version prepared by Licensee. - - 3. In the event Licensee prepares a derivative work that is based on - or incorporates Python or any part thereof, and wants to make - the derivative work available to others as provided herein, then - Licensee hereby agrees to include in any such work a brief summary of - the changes made to Python. - - 4. PSF is making Python available to Licensee on an "AS IS" - basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR - IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND - DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS - FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT - INFRINGE ANY THIRD PARTY RIGHTS. - - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON - FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS - A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, - OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - - 6. This License Agreement will automatically terminate upon a material - breach of its terms and conditions. - - 7. Nothing in this License Agreement shall be deemed to create any - relationship of agency, partnership, or joint venture between PSF and - Licensee. This License Agreement does not grant permission to use PSF - trademarks or trade name in a trademark sense to endorse or promote - products or services of Licensee, or any third party. - - 8. By copying, installing or otherwise using Python, Licensee - agrees to be bound by the terms and conditions of this License - Agreement. - The documentation portion of Pyramid (the rendered contents of the "docs" directory of a software distribution or checkout) is supplied under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 diff --git a/pyramid/compat.py b/pyramid/compat.py new file mode 100644 index 000000000..7d723715e --- /dev/null +++ b/pyramid/compat.py @@ -0,0 +1,8 @@ +try: + import json +except ImportError: # pragma: no cover + try: + import simplejson as json + except NotImplementedError: + from django.utils import simplejson as json # GAE + diff --git a/pyramid/compat/__init__.py b/pyramid/compat/__init__.py deleted file mode 100644 index 096fb3ddf..000000000 --- a/pyramid/compat/__init__.py +++ /dev/null @@ -1,157 +0,0 @@ -# Some code in this file was lifted wholesale from Django -# (see -# http://code.djangoproject.com/browser/django/trunk/LICENSE for -# license text; BSD-like) - -# License for code in this file that was taken from Python 2.5. - -# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -# -------------------------------------------- -# -# 1. This LICENSE AGREEMENT is between the Python Software Foundation -# ("PSF"), and the Individual or Organization ("Licensee") accessing and -# otherwise using this software ("Python") in source or binary form and -# its associated documentation. -# -# 2. Subject to the terms and conditions of this License Agreement, PSF -# hereby grants Licensee a nonexclusive, royalty-free, world-wide -# license to reproduce, analyze, test, perform and/or display publicly, -# prepare derivative works, distribute, and otherwise use Python -# alone or in any derivative version, provided, however, that PSF's -# License Agreement and PSF's notice of copyright, i.e., "Copyright (c) -# 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; -# All Rights Reserved" are retained in Python alone or in any derivative -# version prepared by Licensee. -# -# 3. In the event Licensee prepares a derivative work that is based on -# or incorporates Python or any part thereof, and wants to make -# the derivative work available to others as provided herein, then -# Licensee hereby agrees to include in any such work a brief summary of -# the changes made to Python. -# -# 4. PSF is making Python available to Licensee on an "AS IS" -# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -# INFRINGE ANY THIRD PARTY RIGHTS. -# -# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. -# -# 6. This License Agreement will automatically terminate upon a material -# breach of its terms and conditions. -# -# 7. Nothing in this License Agreement shall be deemed to create any -# relationship of agency, partnership, or joint venture between PSF and -# Licensee. This License Agreement does not grant permission to use PSF -# trademarks or trade name in a trademark sense to endorse or promote -# products or services of Licensee, or any third party. -# -# 8. By copying, installing or otherwise using Python, Licensee -# agrees to be bound by the terms and conditions of this License -# Agreement. - - -try: # pragma: no cover - from functools import wraps -except ImportError: #pragma no cover - # < 2.5 - def curry(_curried_func, *args, **kwargs): - def _curried(*moreargs, **morekwargs): - return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) - return _curried - - ### Begin from Python 2.5 functools.py ################################### - # Summary of changes made to the Python 2.5 code below: - # * swapped ``partial`` for ``curry`` to maintain backwards-compatibility - # in Django. - # * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except - # block to make it compatible with Python 2.3, which doesn't allow - # assigning to ``__name__``. - - # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software - # Foundation. - # All Rights Reserved. - ########################################################################## - - # update_wrapper() and wraps() are tools to help write - # wrapper functions that can handle naive introspection - - WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') - WRAPPER_UPDATES = ('__dict__',) - def update_wrapper(wrapper, - wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Update a wrapper function to look like the wrapped function - - wrapper is the function to be updated - wrapped is the original function - assigned is a tuple naming the attributes assigned directly - from the wrapped function to the wrapper function (defaults to - functools.WRAPPER_ASSIGNMENTS) - updated is a tuple naming the attributes off the wrapper that - are updated with the corresponding attribute from the wrapped - function (defaults to functools.WRAPPER_UPDATES) - """ - for attr in assigned: - setattr(wrapper, attr, getattr(wrapped, attr)) - - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr)) - # Return the wrapper so this can be used as a decorator via curry() - return wrapper - - def wraps(wrapped, - assigned = WRAPPER_ASSIGNMENTS, - updated = WRAPPER_UPDATES): - """Decorator factory to apply update_wrapper() to a wrapper function - - Returns a decorator that invokes update_wrapper() with the decorated - function as the wrapper argument and the arguments to wraps() as the - remaining arguments. Default arguments are as for update_wrapper(). - This is a convenience function to simplify applying curry() to - update_wrapper(). - """ - return curry(update_wrapper, wrapped=wrapped, - assigned=assigned, updated=updated) - -### End from Python 2.5 functools.py ########################################## - -try: - all = all -except NameError: # pragma: no cover - def all(iterable): - for element in iterable: - if not element: - return False - return True - -try: - import json -except ImportError: # pragma: no cover - try: - import simplejson as json - except NotImplementedError: - from django.utils import simplejson as json # GAE - - - -try: - from hashlib import md5 -except ImportError: # pragma: no cover - import md5 - md5 = md5.new - -try: - any = any # make importable -except NameError: # pragma: no cover - def any(L): - for thing in L: - if thing: - return True - return False - diff --git a/pyramid/config.py b/pyramid/config.py index 29da51875..59ecd5bc8 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -5,6 +5,7 @@ import sys import types import traceback import warnings +from hashlib import md5 import venusian @@ -52,9 +53,6 @@ from pyramid.interfaces import IViewMapperFactory from pyramid import renderers from pyramid.authorization import ACLAuthorizationPolicy -from pyramid.compat import all -from pyramid.compat import md5 -from pyramid.compat import any from pyramid.events import ApplicationCreated from pyramid.exceptions import ConfigurationError from pyramid.exceptions import PredicateMismatch diff --git a/pyramid/tests/test_compat.py b/pyramid/tests/test_compat.py deleted file mode 100644 index 16fc28826..000000000 --- a/pyramid/tests/test_compat.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest - -class TestAliases(unittest.TestCase): - def test_all(self): - from pyramid.compat import all - self.assertEqual(all([True, True]), True) - self.assertEqual(all([False, False]), False) - self.assertEqual(all([False, True]), False) - diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index a50434f7e..ff226617e 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -1017,7 +1017,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper, view) def test_add_view_same_phash_overrides_existing_single_view(self): - from pyramid.compat import md5 + from hashlib import md5 from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1040,7 +1040,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_same_phash_overrides_existing_single_view(self): - from pyramid.compat import md5 + from hashlib import md5 from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index 230b18e54..2cbbb8709 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -5,7 +5,6 @@ from zope.interface import implements from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IRoute -from pyramid.compat import all from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.traversal import traversal_path diff --git a/pyramid/wsgi.py b/pyramid/wsgi.py index e4c61ff63..3bbe31790 100644 --- a/pyramid/wsgi.py +++ b/pyramid/wsgi.py @@ -1,4 +1,4 @@ -from pyramid.compat import wraps +from functools import wraps from pyramid.request import call_app_with_subpath_as_path_info def wsgiapp(wrapped): -- cgit v1.2.3 From 0b0b2065558649c0af65a08e94cd99894889bea3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 14:41:22 -0400 Subject: urllib2 example of creating a request suitable for producing a json body --- docs/narr/webob.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 6e8c39523..106024db3 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -296,6 +296,20 @@ For the above view, printed to the console will be: {u'a': 1} +For bonus points, here's a bit of client-side code that will produce a +request that has a body suitable for reading via ``request.json_body`` using +Python's ``urllib2`` instead of a Javascript AJAX request: + +.. code-block:: python + + import urllib2 + import json + + json_payload = json.dumps({'a':1}) + headers = {'Content-Type':'application/json; charset=utf-8'} + req = urllib2.Request('http://localhost:6543/', json_payload, headers) + resp = urllib2.urlopen(req) + More Details ++++++++++++ -- cgit v1.2.3 From 0eaa60f8bd4c0e76ec35fd9cd25b0f90fb9019e0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 19:36:21 -0400 Subject: comment --- pyramid/renderers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index b201d32c2..30c6ee54e 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -412,6 +412,8 @@ class RendererHelper(object): return self._make_response(result, request) def _make_response(self, result, request): + # broken out of render_to_response as a separate method for testing + # purposes response = getattr(request, 'response', None) if response is None: # request is None or request is not a pyramid.response.Response -- cgit v1.2.3 From 73c0aee5571bbdd7273033eda3a798d5597a76af Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 19:59:36 -0400 Subject: - Add a deprecation warning for non-API function ``pyramid.renderers.renderer_from_name`` which has seen use in the wild. - Add a ``clone`` method to ``pyramid.renderers.RendererHelper`` for use by the ``pyramid.view.view_config`` decorator. --- CHANGES.txt | 6 ++++++ pyramid/renderers.py | 18 +++++++++++++++++- pyramid/tests/test_renderers.py | 23 ++++++++++++++++++++++- pyramid/tests/test_view.py | 13 +++++++++---- pyramid/view.py | 3 +-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 609f62bd3..bb5828811 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,12 @@ Internals - Remove ``compat`` code that served only the purpose of providing backwards compatibility with Python 2.4. +- Add a deprecation warning for non-API function + ``pyramid.renderers.renderer_from_name`` which has seen use in the wild. + +- Add a ``clone`` method to ``pyramid.renderers.RendererHelper`` for use by + the ``pyramid.view.view_config`` decorator. + Documentation ------------- diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 30c6ee54e..d7ce1b045 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -3,6 +3,7 @@ import pkg_resources import threading from zope.interface import implements +from zope.deprecation import deprecated from pyramid.interfaces import IChameleonLookup from pyramid.interfaces import IChameleonTranslate @@ -335,10 +336,16 @@ def template_renderer_factory(info, impl, lock=registry_lock): lock.release() return lookup(info) -# XXX deprecate def renderer_from_name(path, package=None): return RendererHelper(name=path, package=package).renderer +deprecated( + 'renderer_from_name', + 'The "pyramid.renderers.renderer_from_name" function was never an API. ' + 'However, its use has been observed "in the wild." It will disappear in ' + 'the next major release. To replace it, use the ' + '``pyramid.renderers.get_renderer`` API instead. ') + class RendererHelper(object): implements(IRendererInfo) def __init__(self, name=None, package=None, registry=None): @@ -453,3 +460,12 @@ class RendererHelper(object): response.cache_expires = cache_for return response + def clone(self, name=None, package=None, registry=None): + if name is None: + name = self.name + if package is None: + package = self.package + if registry is None: + registry = self.registry + return self.__class__(name=name, package=package, registry=registry) + diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 18b4caa61..c847c70e2 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -341,11 +341,15 @@ class TestChameleonRendererLookup(unittest.TestCase): class TestRendererFromName(unittest.TestCase): def setUp(self): + from zope.deprecation import __show__ + __show__.off() self.config = cleanUp() def tearDown(self): cleanUp() - + from zope.deprecation import __show__ + __show__.on() + def _callFUT(self, path, package=None): from pyramid.renderers import renderer_from_name return renderer_from_name(path, package) @@ -687,6 +691,23 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(response.status, '406 You Lose') self.assertEqual(response.body, 'abc') + def test_clone_noargs(self): + helper = self._makeOne('name', 'package', 'registry') + cloned_helper = helper.clone() + self.assertEqual(cloned_helper.name, 'name') + self.assertEqual(cloned_helper.package, 'package') + self.assertEqual(cloned_helper.registry, 'registry') + self.assertFalse(helper is cloned_helper) + + def test_clone_allargs(self): + helper = self._makeOne('name', 'package', 'registry') + cloned_helper = helper.clone(name='name2', package='package2', + registry='registry2') + self.assertEqual(cloned_helper.name, 'name2') + self.assertEqual(cloned_helper.package, 'package2') + self.assertEqual(cloned_helper.registry, 'registry2') + self.assertFalse(helper is cloned_helper) + class Test_render(unittest.TestCase): def setUp(self): self.config = testing.setUp() diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 8e5861e7b..01aa70844 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -399,10 +399,17 @@ class TestViewConfigDecorator(unittest.TestCase): # see https://github.com/Pylons/pyramid/pull/234 from pyramid.interfaces import IRendererInfo import pyramid.tests + outerself = self class DummyRendererHelper(object): implements(IRendererInfo) name = 'fixtures/minimal.pt' package = pyramid.tests + def clone(self, name=None, package=None, registry=None): + outerself.assertEqual(name, self.name) + outerself.assertEqual(package, self.package) + outerself.assertEqual(registry, context.config.registry) + self.cloned = True + return self renderer_helper = DummyRendererHelper() decorator = self._makeOne(renderer=renderer_helper) venusian = DummyVenusian() @@ -414,10 +421,8 @@ class TestViewConfigDecorator(unittest.TestCase): settings = call_venusian(venusian, context) self.assertEqual(len(settings), 1) renderer = settings[0]['renderer'] - self.assertFalse(renderer is renderer_helper) - self.assertEqual(renderer.name, 'fixtures/minimal.pt') - self.assertEqual(renderer.package, pyramid.tests) - self.assertEqual(renderer.registry, context.config.registry) + self.assertTrue(renderer is renderer_helper) + self.assertTrue(renderer.cloned) class Test_append_slash_notfound_view(BaseTest, unittest.TestCase): def _callFUT(self, context, request): diff --git a/pyramid/view.py b/pyramid/view.py index 1b59a2ed9..1573ee34c 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -1,6 +1,5 @@ import mimetypes import venusian -import warnings from zope.interface import providedBy from zope.deprecation import deprecated @@ -226,7 +225,7 @@ class view_config(object): elif IRendererInfo.providedBy(renderer): # create a new rendererinfo to clear out old registry on a # rescan, see https://github.com/Pylons/pyramid/pull/234 - renderer = RendererHelper(name=renderer.name, + renderer = renderer.clone(name=renderer.name, package=info.module, registry=context.config.registry) settings['renderer'] = renderer -- cgit v1.2.3 From aa2fe1b0a02ba4edde4d285ec0a5a6ec545b7fec Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 22:02:37 -0400 Subject: - Added the ``pyramid.renderers.null_renderer`` object as an API. The null renderer is an object that can be used in advanced integration cases as input to the view configuration ``renderer=`` argument. When the null renderer is used as a view renderer argument, Pyramid avoids converting the view callable result into a Response object. This is useful if you want to reuse the view configuration and lookup machinery outside the context of its use by the Pyramid router. This feature was added for consumption by the ``pyramid_rpc`` package, which uses view configuration and lookup outside the context of a router in exactly this way. ``pyramid_rpc`` has been broken under 1.1 since 1.1b1; adding it allows us to make it work again. --- CHANGES.txt | 15 ++ docs/api/renderers.rst | 9 + pyramid/config.py | 13 +- pyramid/interfaces.py | 1 - pyramid/renderers.py | 19 ++ pyramid/tests/grokkedapp/__init__.py | 34 +-- pyramid/tests/grokkedapp/another.py | 24 +- pyramid/tests/grokkedapp/pod/notinit.py | 3 +- pyramid/tests/grokkedapp/subpackage/__init__.py | 3 +- pyramid/tests/grokkedapp/subpackage/notinit.py | 3 +- .../subpackage/subsubpackage/__init__.py | 3 +- pyramid/tests/test_config.py | 268 ++++++++++++++------- pyramid/tests/test_renderers.py | 33 +++ 13 files changed, 309 insertions(+), 119 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bb5828811..5e2fb2970 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,21 @@ Next release ============ +Features +-------- + +- Added the ``pyramid.renderers.null_renderer`` object as an API. The null + renderer is an object that can be used in advanced integration cases as + input to the view configuration ``renderer=`` argument. When the null + renderer is used as a view renderer argument, Pyramid avoids converting the + view callable result into a Response object. This is useful if you want to + reuse the view configuration and lookup machinery outside the context of + its use by the Pyramid router. This feature was added for consumption by + the ``pyramid_rpc`` package, which uses view configuration and lookup + outside the context of a router in exactly this way. ``pyramid_rpc`` has + been broken under 1.1 since 1.1b1; adding it allows us to make it work + again. + Internals --------- diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst index c13694219..15670c46e 100644 --- a/docs/api/renderers.rst +++ b/docs/api/renderers.rst @@ -13,3 +13,12 @@ .. autoclass:: JSONP +.. attribute:: null_renderer + + An object that can be used in advanced integration cases as input to the + view configuration ``renderer=`` argument. When the null renderer is used + as a view renderer argument, Pyramid avoids converting the view callable + result into a Response object. This is useful if you want to reuse the + view configuration and lookup machinery outside the context of its use by + the Pyramid router (e.g. the package named ``pyramid_rpc`` does this). + diff --git a/pyramid/config.py b/pyramid/config.py index 59ecd5bc8..cad853674 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -2124,7 +2124,7 @@ class Configurator(object): context = getattr(request, 'context', None) return view(context, request) return self.add_view(bwcompat_view, context=HTTPForbidden, - wrapper=wrapper) + wrapper=wrapper, renderer=renderer) @action_method def set_notfound_view(self, view=None, attr=None, renderer=None, @@ -2165,7 +2165,7 @@ class Configurator(object): context = getattr(request, 'context', None) return view(context, request) return self.add_view(bwcompat_view, context=HTTPNotFound, - wrapper=wrapper) + wrapper=wrapper, renderer=renderer) @action_method def set_request_factory(self, factory): @@ -3104,7 +3104,8 @@ class ViewDeriver(object): @wraps_view def rendered_view(self, view): - # one way or another this wrapper must produce a Response + # one way or another this wrapper must produce a Response (unless + # the renderer is a NullRendererHelper) renderer = self.kw.get('renderer') if renderer is None: # register a default renderer if you want super-dynamic @@ -3112,6 +3113,8 @@ class ViewDeriver(object): # override_renderer to work if a renderer is left unspecified for # a view registration. return self._response_resolved_view(view) + if renderer is renderers.null_renderer: + return view return self._rendered_view(view, renderer) def _rendered_view(self, view, view_renderer): @@ -3142,10 +3145,6 @@ class ViewDeriver(object): def _response_resolved_view(self, view): registry = self.registry - if hasattr(registry, '_dont_resolve_responses'): - # for Pyramid unit tests only - return view - def viewresult_to_response(context, request): result = view(context, request) response = registry.queryAdapterOrSelf(result, IResponse) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 4ef58846b..d3a67e1aa 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -866,4 +866,3 @@ class IRendererInfo(Interface): settings = Attribute('The deployment settings dictionary related ' 'to the current application') - diff --git a/pyramid/renderers.py b/pyramid/renderers.py index d7ce1b045..52459ee90 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -469,3 +469,22 @@ class RendererHelper(object): registry = self.registry return self.__class__(name=name, package=package, registry=registry) +class NullRendererHelper(RendererHelper): + """ Special renderer helper that has render_* methods which simply return + the value they are fed rather than converting them to response objects; + useful for testing purposes and special case view configuration + registrations that want to use the view configuration machinery but do + not want actual rendering to happen .""" + def render_view(self, request, value, view, context): + return value + + def render(self, value, system_values, request=None): + return value + + def render_to_response(self, value, system_values, request=None): + return value + + def clone(self, name=None, package=None, registry=None): + return self + +null_renderer = NullRendererHelper() diff --git a/pyramid/tests/grokkedapp/__init__.py b/pyramid/tests/grokkedapp/__init__.py index 1411e4c49..562413a41 100644 --- a/pyramid/tests/grokkedapp/__init__.py +++ b/pyramid/tests/grokkedapp/__init__.py @@ -1,15 +1,16 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config() +@view_config(renderer=null_renderer) def grokked(context, request): return 'grokked' -@view_config(request_method='POST') +@view_config(request_method='POST', renderer=null_renderer) def grokked_post(context, request): return 'grokked_post' -@view_config(name='stacked2') -@view_config(name='stacked1') +@view_config(name='stacked2', renderer=null_renderer) +@view_config(name='stacked1', renderer=null_renderer) def stacked(context, request): return 'stacked' @@ -21,8 +22,10 @@ class stacked_class(object): def __call__(self): return 'stacked_class' -stacked_class = view_config(name='stacked_class1')(stacked_class) -stacked_class = view_config(name='stacked_class2')(stacked_class) +stacked_class = view_config(name='stacked_class1', + renderer=null_renderer)(stacked_class) +stacked_class = view_config(name='stacked_class2', + renderer=null_renderer)(stacked_class) class oldstyle_grokked_class: def __init__(self, context, request): @@ -32,7 +35,8 @@ class oldstyle_grokked_class: def __call__(self): return 'oldstyle_grokked_class' -oldstyle_grokked_class = view_config(name='oldstyle_grokked_class')( +oldstyle_grokked_class = view_config(name='oldstyle_grokked_class', + renderer=null_renderer)( oldstyle_grokked_class) class grokked_class(object): @@ -43,17 +47,19 @@ class grokked_class(object): def __call__(self): return 'grokked_class' -grokked_class = view_config(name='grokked_class')(grokked_class) +grokked_class = view_config(name='grokked_class', + renderer=null_renderer)(grokked_class) class Foo(object): def __call__(self, context, request): return 'grokked_instance' grokked_instance = Foo() -grokked_instance = view_config(name='grokked_instance')(grokked_instance) +grokked_instance = view_config(name='grokked_instance', + renderer=null_renderer)(grokked_instance) class Base(object): - @view_config(name='basemethod') + @view_config(name='basemethod', renderer=null_renderer) def basemethod(self): """ """ @@ -62,16 +68,16 @@ class MethodViews(Base): self.context = context self.request = request - @view_config(name='method1') + @view_config(name='method1', renderer=null_renderer) def method1(self): return 'method1' - @view_config(name='method2') + @view_config(name='method2', renderer=null_renderer) def method2(self): return 'method2' - @view_config(name='stacked_method2') - @view_config(name='stacked_method1') + @view_config(name='stacked_method2', renderer=null_renderer) + @view_config(name='stacked_method1', renderer=null_renderer) def stacked(self): return 'stacked_method' diff --git a/pyramid/tests/grokkedapp/another.py b/pyramid/tests/grokkedapp/another.py index 48fe81798..529821b5c 100644 --- a/pyramid/tests/grokkedapp/another.py +++ b/pyramid/tests/grokkedapp/another.py @@ -1,15 +1,16 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config(name='another') +@view_config(name='another', renderer=null_renderer) def grokked(context, request): return 'another_grokked' -@view_config(request_method='POST', name='another') +@view_config(request_method='POST', name='another', renderer=null_renderer) def grokked_post(context, request): return 'another_grokked_post' -@view_config(name='another_stacked2') -@view_config(name='another_stacked1') +@view_config(name='another_stacked2', renderer=null_renderer) +@view_config(name='another_stacked1', renderer=null_renderer) def stacked(context, request): return 'another_stacked' @@ -21,8 +22,10 @@ class stacked_class(object): def __call__(self): return 'another_stacked_class' -stacked_class = view_config(name='another_stacked_class1')(stacked_class) -stacked_class = view_config(name='another_stacked_class2')(stacked_class) +stacked_class = view_config(name='another_stacked_class1', + renderer=null_renderer)(stacked_class) +stacked_class = view_config(name='another_stacked_class2', + renderer=null_renderer)(stacked_class) class oldstyle_grokked_class: def __init__(self, context, request): @@ -32,7 +35,8 @@ class oldstyle_grokked_class: def __call__(self): return 'another_oldstyle_grokked_class' -oldstyle_grokked_class = view_config(name='another_oldstyle_grokked_class')( +oldstyle_grokked_class = view_config(name='another_oldstyle_grokked_class', + renderer=null_renderer)( oldstyle_grokked_class) class grokked_class(object): @@ -43,14 +47,16 @@ class grokked_class(object): def __call__(self): return 'another_grokked_class' -grokked_class = view_config(name='another_grokked_class')(grokked_class) +grokked_class = view_config(name='another_grokked_class', + renderer=null_renderer)(grokked_class) class Foo(object): def __call__(self, context, request): return 'another_grokked_instance' grokked_instance = Foo() -grokked_instance = view_config(name='another_grokked_instance')( +grokked_instance = view_config(name='another_grokked_instance', + renderer=null_renderer)( grokked_instance) # ungrokkable diff --git a/pyramid/tests/grokkedapp/pod/notinit.py b/pyramid/tests/grokkedapp/pod/notinit.py index 3d01f92d5..91dcd161b 100644 --- a/pyramid/tests/grokkedapp/pod/notinit.py +++ b/pyramid/tests/grokkedapp/pod/notinit.py @@ -1,5 +1,6 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config(name='pod_notinit') +@view_config(name='pod_notinit', renderer=null_renderer) def subpackage_notinit(context, request): return 'pod_notinit' diff --git a/pyramid/tests/grokkedapp/subpackage/__init__.py b/pyramid/tests/grokkedapp/subpackage/__init__.py index 3e332913a..9e0ddacbd 100644 --- a/pyramid/tests/grokkedapp/subpackage/__init__.py +++ b/pyramid/tests/grokkedapp/subpackage/__init__.py @@ -1,5 +1,6 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config(name='subpackage_init') +@view_config(name='subpackage_init', renderer=null_renderer) def subpackage_init(context, request): return 'subpackage_init' diff --git a/pyramid/tests/grokkedapp/subpackage/notinit.py b/pyramid/tests/grokkedapp/subpackage/notinit.py index 41f0c5ea8..f7edd0c68 100644 --- a/pyramid/tests/grokkedapp/subpackage/notinit.py +++ b/pyramid/tests/grokkedapp/subpackage/notinit.py @@ -1,5 +1,6 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config(name='subpackage_notinit') +@view_config(name='subpackage_notinit', renderer=null_renderer) def subpackage_notinit(context, request): return 'subpackage_notinit' diff --git a/pyramid/tests/grokkedapp/subpackage/subsubpackage/__init__.py b/pyramid/tests/grokkedapp/subpackage/subsubpackage/__init__.py index ade9644ec..fdda0dffe 100644 --- a/pyramid/tests/grokkedapp/subpackage/subsubpackage/__init__.py +++ b/pyramid/tests/grokkedapp/subpackage/subsubpackage/__init__.py @@ -1,5 +1,6 @@ from pyramid.view import view_config +from pyramid.renderers import null_renderer -@view_config(name='subsubpackage_init') +@view_config(name='subsubpackage_init', renderer=null_renderer) def subpackage_init(context, request): return 'subsubpackage_init' diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ff226617e..88c39f36d 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -11,7 +11,6 @@ class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) - config.registry._dont_resolve_responses = True return config def _registerRenderer(self, config, name='.txt'): @@ -364,6 +363,7 @@ class ConfiguratorTests(unittest.TestCase): config.registry.queryAdapter(response, IResponse) is response) def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPNotFound @@ -373,7 +373,7 @@ class ConfiguratorTests(unittest.TestCase): config.setup_registry() # registers IExceptionResponse default view def myview(context, request): return 'OK' - config.add_view(myview, context=HTTPNotFound) + config.add_view(myview, context=HTTPNotFound, renderer=null_renderer) request = self._makeRequest(config) view = self._getViewCallable(config, ctx_iface=implementedBy(HTTPNotFound), @@ -778,12 +778,14 @@ class ConfiguratorTests(unittest.TestCase): None, True, True) def test_add_view_with_request_type(self): + from pyramid.renderers import null_renderer from zope.interface import directlyProvides from pyramid.interfaces import IRequest view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, - request_type='pyramid.interfaces.IRequest') + request_type='pyramid.interfaces.IRequest', + renderer=null_renderer) wrapper = self._getViewCallable(config) request = DummyRequest() self._assertNotFound(wrapper, None, request) @@ -809,29 +811,34 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper.__doc__, view.__doc__) def test_add_view_view_callable_dottedname(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) - config.add_view(view='pyramid.tests.test_config.dummy_view') + config.add_view(view='pyramid.tests.test_config.dummy_view', + renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_function_callable(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_function_callable_requestonly(self): + from pyramid.renderers import null_renderer def view(request): return 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_decorator(self): + from pyramid.renderers import null_renderer def view(request): """ ABC """ return 'OK' @@ -840,7 +847,8 @@ class ConfiguratorTests(unittest.TestCase): return fn(context, request) return inner config = self._makeOne(autocommit=True) - config.add_view(view=view, decorator=view_wrapper) + config.add_view(view=view, decorator=view_wrapper, + renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(wrapper is view) self.assertEqual(wrapper.__doc__, view.__doc__) @@ -869,30 +877,33 @@ class ConfiguratorTests(unittest.TestCase): assert_similar_datetime(expires, when) def test_add_view_as_instance(self): + from pyramid.renderers import null_renderer class AView: def __call__(self, context, request): """ """ return 'OK' view = AView() config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_instance_requestonly(self): + from pyramid.renderers import null_renderer class AView: def __call__(self, request): """ """ return 'OK' view = AView() config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_oldstyle_class(self): + from pyramid.renderers import null_renderer class view: def __init__(self, context, request): self.context = context @@ -901,7 +912,7 @@ class ConfiguratorTests(unittest.TestCase): def __call__(self): return 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) @@ -909,6 +920,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(request.__view__.__class__, view) def test_add_view_as_oldstyle_class_requestonly(self): + from pyramid.renderers import null_renderer class view: def __init__(self, request): self.request = request @@ -916,7 +928,7 @@ class ConfiguratorTests(unittest.TestCase): def __call__(self): return 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) @@ -925,70 +937,79 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(request.__view__.__class__, view) def test_add_view_context_as_class(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy view = lambda *arg: 'OK' class Foo: pass config = self._makeOne(autocommit=True) - config.add_view(context=Foo, view=view) + config.add_view(context=Foo, view=view, renderer=null_renderer) foo = implementedBy(Foo) wrapper = self._getViewCallable(config, foo) self.assertEqual(wrapper, view) def test_add_view_context_as_iface(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(context=IDummy, view=view) + config.add_view(context=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_context_as_dottedname(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(context='pyramid.tests.test_config.IDummy', - view=view) + view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_for__as_dottedname(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(for_='pyramid.tests.test_config.IDummy', - view=view) + view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_for_as_class(self): # ``for_`` is older spelling for ``context`` + from pyramid.renderers import null_renderer from zope.interface import implementedBy view = lambda *arg: 'OK' class Foo: pass config = self._makeOne(autocommit=True) - config.add_view(for_=Foo, view=view) + config.add_view(for_=Foo, view=view, renderer=null_renderer) foo = implementedBy(Foo) wrapper = self._getViewCallable(config, foo) self.assertEqual(wrapper, view) def test_add_view_for_as_iface(self): # ``for_`` is older spelling for ``context`` + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(for_=IDummy, view=view) + config.add_view(for_=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_context_trumps_for(self): # ``for_`` is older spelling for ``context`` + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) class Foo: pass - config.add_view(context=IDummy, for_=Foo, view=view) + config.add_view(context=IDummy, for_=Foo, view=view, + renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_register_secured_view(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView @@ -996,13 +1017,14 @@ class ConfiguratorTests(unittest.TestCase): view = lambda *arg: 'OK' view.__call_permissive__ = view config = self._makeOne(autocommit=True) - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = config.registry.adapters.lookup( (IViewClassifier, IRequest, Interface), ISecuredView, name='', default=None) self.assertEqual(wrapper, view) def test_add_view_exception_register_secured_view(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1010,13 +1032,14 @@ class ConfiguratorTests(unittest.TestCase): view = lambda *arg: 'OK' view.__call_permissive__ = view config = self._makeOne(autocommit=True) - config.add_view(view=view, context=RuntimeError) + config.add_view(view=view, context=RuntimeError, renderer=null_renderer) wrapper = config.registry.adapters.lookup( (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='', default=None) self.assertEqual(wrapper, view) def test_add_view_same_phash_overrides_existing_single_view(self): + from pyramid.renderers import null_renderer from hashlib import md5 from zope.interface import Interface from pyramid.interfaces import IRequest @@ -1032,7 +1055,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview, xhr=True) + config.add_view(view=newview, xhr=True, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() @@ -1040,6 +1063,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_same_phash_overrides_existing_single_view(self): + from pyramid.renderers import null_renderer from hashlib import md5 from zope.interface import implementedBy from pyramid.interfaces import IRequest @@ -1057,7 +1081,8 @@ class ConfiguratorTests(unittest.TestCase): IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview, xhr=True, context=RuntimeError) + config.add_view(view=newview, xhr=True, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) @@ -1066,6 +1091,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_default_phash_overrides_no_phash(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1077,7 +1103,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview) + config.add_view(view=newview, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() @@ -1085,6 +1111,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_default_phash_overrides_no_phash(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1098,7 +1125,8 @@ class ConfiguratorTests(unittest.TestCase): IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview, context=RuntimeError) + config.add_view(view=newview, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) @@ -1107,6 +1135,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_default_phash_overrides_default_phash(self): + from pyramid.renderers import null_renderer from pyramid.config import DEFAULT_PHASH from zope.interface import Interface from pyramid.interfaces import IRequest @@ -1120,7 +1149,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview) + config.add_view(view=newview, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() @@ -1128,6 +1157,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_default_phash_overrides_default_phash(self): + from pyramid.renderers import null_renderer from pyramid.config import DEFAULT_PHASH from zope.interface import implementedBy from pyramid.interfaces import IRequest @@ -1143,7 +1173,8 @@ class ConfiguratorTests(unittest.TestCase): IView, name='') def newview(context, request): return 'OK' - config.add_view(view=newview, context=RuntimeError) + config.add_view(view=newview, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) @@ -1152,6 +1183,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_multiview_replaces_existing_view(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1162,12 +1194,13 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_view(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1185,13 +1218,15 @@ class ConfiguratorTests(unittest.TestCase): view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') - config.add_view(view=view, context=RuntimeError) + config.add_view(view=view, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_multiview_replaces_existing_securedview(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView @@ -1203,12 +1238,13 @@ class ConfiguratorTests(unittest.TestCase): config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), ISecuredView, name='') - config.add_view(view=view) + config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_securedview(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView @@ -1226,13 +1262,14 @@ class ConfiguratorTests(unittest.TestCase): view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), ISecuredView, name='') - config.add_view(view=view, context=RuntimeError) + config.add_view(view=view, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_accept_multiview_replaces_existing_view(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1245,7 +1282,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') - config.add_view(view=view2, accept='text/html') + config.add_view(view=view2, accept='text/html', renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) @@ -1256,6 +1293,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK2') def test_add_view_exc_with_accept_multiview_replaces_existing_view(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1275,7 +1313,8 @@ class ConfiguratorTests(unittest.TestCase): view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') - config.add_view(view=view2, accept='text/html', context=RuntimeError) + config.add_view(view=view2, accept='text/html', context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) @@ -1287,6 +1326,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK2') def test_add_view_multiview_replaces_existing_view_with___accept__(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1301,7 +1341,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') - config.add_view(view=view2) + config.add_view(view=view2, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) @@ -1312,6 +1352,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_mulview_replaces_existing_view_with___accept__(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1333,7 +1374,8 @@ class ConfiguratorTests(unittest.TestCase): view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') - config.add_view(view=view2, context=RuntimeError) + config.add_view(view=view2, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) @@ -1345,6 +1387,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_multiview_replaces_multiview(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IMultiView @@ -1355,13 +1398,14 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Interface), IMultiView, name='') view2 = lambda *arg: 'OK2' - config.add_view(view=view2) + config.add_view(view=view2, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual([x[:2] for x in wrapper.views], [(view2, None)]) self.assertEqual(wrapper(None, None), 'OK1') def test_add_view_exc_multiview_replaces_multiview(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IMultiView @@ -1378,7 +1422,8 @@ class ConfiguratorTests(unittest.TestCase): (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IMultiView, name='') view2 = lambda *arg: 'OK2' - config.add_view(view=view2, context=RuntimeError) + config.add_view(view=view2, context=RuntimeError, + renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) @@ -1386,6 +1431,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, None), 'OK1') def test_add_view_multiview_context_superclass_then_subclass(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1400,7 +1446,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, ISuper), IView, name='') - config.add_view(view=view2, for_=ISub) + config.add_view(view=view2, for_=ISub, renderer=null_renderer) wrapper = self._getViewCallable(config, ISuper, IRequest) self.assertFalse(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') @@ -1409,6 +1455,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper(None, None), 'OK2') def test_add_view_multiview_exception_superclass_then_subclass(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -1426,7 +1473,7 @@ class ConfiguratorTests(unittest.TestCase): view, (IViewClassifier, IRequest, Super), IView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, Super), IView, name='') - config.add_view(view=view2, for_=Sub) + config.add_view(view=view2, for_=Sub, renderer=null_renderer) wrapper = self._getViewCallable( config, implementedBy(Super), IRequest) wrapper_exc_view = self._getViewCallable( @@ -1443,6 +1490,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper_exc_view(None, None), 'OK2') def test_add_view_multiview_call_ordering(self): + from pyramid.renderers import null_renderer as nr from zope.interface import directlyProvides def view1(context, request): return 'view1' def view2(context, request): return 'view2' @@ -1453,15 +1501,18 @@ class ConfiguratorTests(unittest.TestCase): def view7(context, request): return 'view7' def view8(context, request): return 'view8' config = self._makeOne(autocommit=True) - config.add_view(view=view1) - config.add_view(view=view2, request_method='POST') - config.add_view(view=view3,request_param='param') - config.add_view(view=view4, containment=IDummy) - config.add_view(view=view5, request_method='POST',request_param='param') - config.add_view(view=view6, request_method='POST', containment=IDummy) - config.add_view(view=view7, request_param='param', containment=IDummy) + config.add_view(view=view1, renderer=nr) + config.add_view(view=view2, request_method='POST', renderer=nr) + config.add_view(view=view3,request_param='param', renderer=nr) + config.add_view(view=view4, containment=IDummy, renderer=nr) + config.add_view(view=view5, request_method='POST', + request_param='param', renderer=nr) + config.add_view(view=view6, request_method='POST', containment=IDummy, + renderer=nr) + config.add_view(view=view7, request_param='param', containment=IDummy, + renderer=nr) config.add_view(view=view8, request_method='POST',request_param='param', - containment=IDummy) + containment=IDummy, renderer=nr) wrapper = self._getViewCallable(config) @@ -1585,11 +1636,12 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result.settings, settings) def test_add_view_with_request_type_as_iface(self): + from pyramid.renderers import null_renderer from zope.interface import directlyProvides def view(context, request): return 'OK' config = self._makeOne(autocommit=True) - config.add_view(request_type=IDummy, view=view) + config.add_view(request_type=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, None) request = self._makeRequest(config) directlyProvides(request, IDummy) @@ -1604,10 +1656,11 @@ class ConfiguratorTests(unittest.TestCase): config.add_view, view, '', None, None, object) def test_add_view_with_route_name(self): + from pyramid.renderers import null_renderer from zope.component import ComponentLookupError view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, route_name='foo') + config.add_view(view=view, route_name='foo', renderer=null_renderer) self.assertEqual(len(config.registry.deferred_route_views), 1) infos = config.registry.deferred_route_views['foo'] self.assertEqual(len(infos), 1) @@ -1647,11 +1700,13 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(info['custom_predicates'], ('123',)) def test_add_view_with_route_name_exception(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from zope.component import ComponentLookupError view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, route_name='foo', context=RuntimeError) + config.add_view(view=view, route_name='foo', context=RuntimeError, + renderer=null_renderer) self.assertEqual(len(config.registry.deferred_route_views), 1) infos = config.registry.deferred_route_views['foo'] self.assertEqual(len(infos), 1) @@ -1678,9 +1733,11 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(wrapper_exc_view(None, None), 'OK') def test_add_view_with_request_method_true(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, request_method='POST') + config.add_view(view=view, request_method='POST', + renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'POST' @@ -1696,9 +1753,10 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_request_param_noval_true(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, request_param='abc') + config.add_view(view=view, request_param='abc', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {'abc':''} @@ -1714,9 +1772,11 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_request_param_val_true(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, request_param='abc=123') + config.add_view(view=view, request_param='abc=123', + renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {'abc':'123'} @@ -1732,9 +1792,10 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_xhr_true(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, xhr=True) + config.add_view(view=view, xhr=True, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.is_xhr = True @@ -1757,9 +1818,10 @@ class ConfiguratorTests(unittest.TestCase): config.add_view, view=view, header='Host:a\\') def test_add_view_with_header_noval_match(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, header='Host') + config.add_view(view=view, header='Host', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'Host':'whatever'} @@ -1775,9 +1837,10 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_header_val_match(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, header=r'Host:\d') + config.add_view(view=view, header=r'Host:\d', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'Host':'1'} @@ -1803,9 +1866,10 @@ class ConfiguratorTests(unittest.TestCase): self.assertRaises(HTTPNotFound, wrapper, None, request) def test_add_view_with_accept_match(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, accept='text/xml') + config.add_view(view=view, accept='text/xml', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.accept = ['text/xml'] @@ -1821,10 +1885,11 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_containment_true(self): + from pyramid.renderers import null_renderer from zope.interface import directlyProvides view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, containment=IDummy) + config.add_view(view=view, containment=IDummy, renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) @@ -1839,12 +1904,14 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, context, None) def test_add_view_with_containment_dottedname(self): + from pyramid.renderers import null_renderer from zope.interface import directlyProvides view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view( view=view, - containment='pyramid.tests.test_config.IDummy') + containment='pyramid.tests.test_config.IDummy', + renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) @@ -1858,9 +1925,10 @@ class ConfiguratorTests(unittest.TestCase): config.add_view, view=view, path_info='\\') def test_add_view_with_path_info_match(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, path_info='/foo') + config.add_view(view=view, path_info='/foo', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.path_info = '/foo' @@ -1876,6 +1944,7 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_custom_predicates_match(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) def pred1(context, request): @@ -1883,7 +1952,8 @@ class ConfiguratorTests(unittest.TestCase): def pred2(context, request): return True predicates = (pred1, pred2) - config.add_view(view=view, custom_predicates=predicates) + config.add_view(view=view, custom_predicates=predicates, + renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(wrapper(None, request), 'OK') @@ -1902,24 +1972,30 @@ class ConfiguratorTests(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_custom_predicate_bests_standard_predicate(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' view2 = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) def pred1(context, request): return True - config.add_view(view=view, custom_predicates=(pred1,)) - config.add_view(view=view2, request_method='GET') + config.add_view(view=view, custom_predicates=(pred1,), + renderer=null_renderer) + config.add_view(view=view2, request_method='GET', + renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' self.assertEqual(wrapper(None, request), 'OK') def test_add_view_custom_more_preds_first_bests_fewer_preds_last(self): + from pyramid.renderers import null_renderer view = lambda *arg: 'OK' view2 = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) - config.add_view(view=view, request_method='GET', xhr=True) - config.add_view(view=view2, request_method='GET') + config.add_view(view=view, request_method='GET', xhr=True, + renderer=null_renderer) + config.add_view(view=view2, request_method='GET', + renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' @@ -1936,6 +2012,7 @@ class ConfiguratorTests(unittest.TestCase): self.assertRaises(ConfigurationConflictError, config.commit) def test_add_view_with_permission(self): + from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' outerself = self class DummyPolicy(object): @@ -1951,12 +2028,13 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(authorization_policy=policy, authentication_policy=policy, autocommit=True) - config.add_view(view=view1, permission='view') + config.add_view(view=view1, permission='view', renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') def test_add_view_with_default_permission_no_explicit_permission(self): + from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' outerself = self class DummyPolicy(object): @@ -1973,19 +2051,20 @@ class ConfiguratorTests(unittest.TestCase): authentication_policy=policy, default_permission='view', autocommit=True) - config.add_view(view=view1) + config.add_view(view=view1, renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') def test_add_view_with_no_default_permission_no_explicit_permission(self): + from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' class DummyPolicy(object): pass # wont be called policy = DummyPolicy() config = self._makeOne(authorization_policy=policy, authentication_policy=policy, autocommit=True) - config.add_view(view=view1) + config.add_view(view=view1, renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') @@ -2185,17 +2264,19 @@ class ConfiguratorTests(unittest.TestCase): raise AssertionError def test_derive_view_function(self): + from pyramid.renderers import null_renderer def view(request): return 'OK' config = self._makeOne() - result = config.derive_view(view) + result = config.derive_view(view, renderer=null_renderer) self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_derive_view_dottedname(self): + from pyramid.renderers import null_renderer config = self._makeOne() result = config.derive_view( - 'pyramid.tests.test_config.dummy_view') + 'pyramid.tests.test_config.dummy_view', renderer=null_renderer) self.assertFalse(result is dummy_view) self.assertEqual(result(None, None), 'OK') @@ -2288,18 +2369,21 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config._ctx.info, 'abc') def test_add_static_here_no_utility_registered(self): + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.static import PackageURLParser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier config = self._makeOne(autocommit=True) - config.add_static_view('static', 'fixtures/static') + config.add_static_view('static', 'fixtures/static', + renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'static/') self._assertRoute(config, 'static/', 'static/*subpath') wrapped = config.registry.adapters.lookup( (IViewClassifier, request_type, Interface), IView, name='') request = self._makeRequest(config) - self.assertEqual(wrapped(None, request).__class__, PackageURLParser) + result = wrapped(None, request) + self.assertEqual(result.__class__, PackageURLParser) def test_add_static_view_package_relative(self): from pyramid.interfaces import IStaticURLInfo @@ -2332,12 +2416,13 @@ class ConfiguratorTests(unittest.TestCase): [('static', static_path, {})]) def test_set_notfound_view(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPNotFound config = self._makeOne(autocommit=True) view = lambda *arg: arg - config.set_notfound_view(view) + config.set_notfound_view(view, renderer=null_renderer) request = self._makeRequest(config) view = self._getViewCallable(config, ctx_iface=implementedBy(HTTPNotFound), @@ -2346,12 +2431,13 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result, (None, request)) def test_set_notfound_view_request_has_context(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPNotFound config = self._makeOne(autocommit=True) view = lambda *arg: arg - config.set_notfound_view(view) + config.set_notfound_view(view, renderer=null_renderer) request = self._makeRequest(config) request.context = 'abc' view = self._getViewCallable(config, @@ -2381,12 +2467,13 @@ class ConfiguratorTests(unittest.TestCase): self.assertTrue('div' in result.body) def test_set_forbidden_view(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPForbidden config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.set_forbidden_view(view) + config.set_forbidden_view(view, renderer=null_renderer) request = self._makeRequest(config) view = self._getViewCallable(config, ctx_iface=implementedBy(HTTPForbidden), @@ -2395,12 +2482,13 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result, 'OK') def test_set_forbidden_view_request_has_context(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPForbidden config = self._makeOne(autocommit=True) view = lambda *arg: arg - config.set_forbidden_view(view) + config.set_forbidden_view(view, renderer=null_renderer) request = self._makeRequest(config) request.context = 'abc' view = self._getViewCallable(config, @@ -3125,13 +3213,14 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(registeredview.__name__, 'view3') def test_autocommit_no_conflicts(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) def view1(request): pass def view2(request): pass def view3(request): pass - config.add_view(view1) - config.add_view(view2) - config.add_view(view3) + config.add_view(view1, renderer=null_renderer) + config.add_view(view2, renderer=null_renderer) + config.add_view(view3, renderer=null_renderer) config.commit() registeredview = self._getViewCallable(config) self.assertEqual(registeredview.__name__, 'view3') @@ -3287,18 +3376,21 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): yield confinst[2] def test_add_route_with_view(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view) + config.add_route('name', 'path', view=view, view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, None, request_type) self.assertEqual(wrapper(None, None), 'OK') self._assertRoute(config, 'name', 'path') def test_add_route_with_view_context(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_context=IDummy) + config.add_route('name', 'path', view=view, view_context=IDummy, + view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, IDummy, request_type) self.assertEqual(wrapper(None, None), 'OK') @@ -3307,10 +3399,12 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): self.assertEqual(wrapper, None) def test_add_route_with_view_exception(self): + from pyramid.renderers import null_renderer from zope.interface import implementedBy config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_context=RuntimeError) + config.add_route('name', 'path', view=view, view_context=RuntimeError, + view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), @@ -3323,9 +3417,11 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): self.assertEqual(wrapper, None) def test_add_route_with_view_for(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, view_for=IDummy) + config.add_route('name', 'path', view=view, view_for=IDummy, + view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, IDummy, request_type) self.assertEqual(wrapper(None, None), 'OK') @@ -3334,9 +3430,11 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): self.assertEqual(wrapper, None) def test_add_route_with_for_(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' - config.add_route('name', 'path', view=view, for_=IDummy) + config.add_route('name', 'path', view=view, for_=IDummy, + view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, IDummy, request_type) self.assertEqual(wrapper(None, None), 'OK') @@ -3356,6 +3454,7 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): self.assertEqual(wrapper(None, None).body, 'Hello!') def test_add_route_with_view_attr(self): + from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) self._registerRenderer(config) class View(object): @@ -3363,7 +3462,8 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): pass def alt(self): return 'OK' - config.add_route('name', 'path', view=View, view_attr='alt') + config.add_route('name', 'path', view=View, view_attr='alt', + view_renderer=null_renderer) request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, None, request_type) self._assertRoute(config, 'name', 'path') diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index c847c70e2..cc36d69bf 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -708,6 +708,39 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(cloned_helper.registry, 'registry2') self.assertFalse(helper is cloned_helper) +class TestNullRendererHelper(unittest.TestCase): + def setUp(self): + self.config = cleanUp() + + def tearDown(self): + cleanUp() + + def _makeOne(self, *arg, **kw): + from pyramid.renderers import NullRendererHelper + return NullRendererHelper(*arg, **kw) + + def test_instance_conforms(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IRendererInfo + helper = self._makeOne() + verifyObject(IRendererInfo, helper) + + def test_render_view(self): + helper = self._makeOne() + self.assertEqual(helper.render_view(None, True, None, None), True) + + def test_render(self): + helper = self._makeOne() + self.assertEqual(helper.render(True, None, None), True) + + def test_render_to_response(self): + helper = self._makeOne() + self.assertEqual(helper.render_to_response(True, None, None), True) + + def test_clone(self): + helper = self._makeOne() + self.assertTrue(helper.clone() is helper) + class Test_render(unittest.TestCase): def setUp(self): self.config = testing.setUp() -- cgit v1.2.3 From 9006b205d907b0b52b59de0eb1e203afdcdbdd51 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 22:05:58 -0400 Subject: add nullrenderer to whatsnew --- docs/whatsnew-1.1.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 9e6d7b5ae..6761aaff8 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -317,6 +317,16 @@ Minor Feature Additions has a ``last`` attribute containing the last registry loaded. This is used by the scripting machinery, and is available for introspection. +- Added the :attr:`pyramid.renderers.null_renderer` object as an API. The + null renderer is an object that can be used in advanced integration cases + as input to the view configuration ``renderer=`` argument. When the null + renderer is used as a view renderer argument, Pyramid avoids converting the + view callable result into a Response object. This is useful if you want to + reuse the view configuration and lookup machinery outside the context of + its use by the Pyramid router. (This feature was added for consumption by + the ``pyramid_rpc`` package, which uses view configuration and lookup + outside the context of a router in exactly this way.) + Backwards Incompatibilities --------------------------- -- cgit v1.2.3 From 39e88a1f2903f840feeff77e572c7bf3efebb875 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 23:08:31 -0400 Subject: - Change all scaffolding templates that point to docs.pylonsproject.org to use ``/projects/pyramid/current`` rather than ``/projects/pyramid/dev``. --- CHANGES.txt | 3 +++ TODO.txt | 3 --- docs/narr/MyProject/myproject/templates/mytemplate.pt | 14 +++++++------- docs/narr/templates.rst | 4 ++-- .../src/authorization/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki/src/basiclayout/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki/src/models/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki/src/tests/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki/src/views/tutorial/templates/mytemplate.pt | 14 +++++++------- .../src/authorization/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki2/src/basiclayout/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki2/src/models/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki2/src/tests/tutorial/templates/mytemplate.pt | 14 +++++++------- .../wiki2/src/views/tutorial/templates/mytemplate.pt | 14 +++++++------- docs/whatsnew-1.0.rst | 4 ++-- .../scaffolds/alchemy/+package+/templates/model.pt_tmpl | 14 +++++++------- pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl | 14 +++++++------- .../routesalchemy/+package+/templates/mytemplate.pt_tmpl | 14 +++++++------- .../starter/+package+/templates/mytemplate.pt_tmpl | 14 +++++++------- .../scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl | 14 +++++++------- 20 files changed, 119 insertions(+), 119 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5e2fb2970..912c198bb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,9 @@ Features been broken under 1.1 since 1.1b1; adding it allows us to make it work again. +- Change all scaffolding templates that point to docs.pylonsproject.org to + use ``/projects/pyramid/current`` rather than ``/projects/pyramid/dev``. + Internals --------- diff --git a/TODO.txt b/TODO.txt index 3a23f3b26..14c52fe3d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -9,9 +9,6 @@ Should-Have - Add narrative docs for wsgiapp and wsgiapp2. -- Symlink to current docs at docs.pylonsproject.org/curr and change all - scaffolds to use it instead of docs.pylonsproject.org/dev - Nice-to-Have ------------ diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt index 632c34876..97f1e1aa3 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt @@ -46,7 +46,7 @@

Search documentation

+ action="http://docs.pylonsproject.org/pyramid/current/search.html">
@@ -60,32 +60,32 @@
  • - + Narrative Documentation
  • - + API Documentation
  • - + Tutorials
  • - + Change History
  • - + Sample Applications
  • - + Support and Development
  • diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 7c575b9bf..af7257466 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -454,7 +454,7 @@ Here's what a simple :term:`Chameleon` ZPT template used under

    Welcome to ${project}, an application generated by the pyramid web application framework.

    @@ -752,7 +752,7 @@ look like:

    Welcome to ${project}, an application generated by the pyramid web application framework.

    diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index c24daa711..c2bc1fd65 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt index d98420680..efe581b59 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt @@ -31,7 +31,7 @@

    Search documentation

    -
    +
    @@ -43,22 +43,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst index 8602c5105..5cd822b0d 100644 --- a/docs/whatsnew-1.0.rst +++ b/docs/whatsnew-1.0.rst @@ -62,8 +62,8 @@ fail if you do nothing to your existing :mod:`repoze.bfg` application. However, you won't have to do much to use your existing BFG applications on Pyramid. There's automation which will change most of your import statements and ZCML declarations. See -http://docs.pylonshq.com/pyramid/dev/tutorials/bfg/index.html for upgrade -instructions. +http://docs.pylonsproject.org/projects/pyramid/current/tutorials/bfg/index.html +for upgrade instructions. Pylons 1 users will need to do more work to use Pyramid, as Pyramid shares no "DNA" with Pylons. It is hoped that over time documentation and upgrade code diff --git a/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl index 9302326f5..263999c5e 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl @@ -32,7 +32,7 @@

    Search documentation

    -
    +
    @@ -50,22 +50,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl index b0ed476cc..3bf3da34e 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl @@ -32,7 +32,7 @@

    Search documentation

    -
    +
    @@ -48,22 +48,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl index 4fbc554e9..b24c109d7 100644 --- a/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl @@ -32,7 +32,7 @@

    Search documentation

    -
    +
    @@ -44,22 +44,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl index 4fbc554e9..b24c109d7 100644 --- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl @@ -32,7 +32,7 @@

    Search documentation

    -
    +
    @@ -44,22 +44,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl index 4fbc554e9..b24c109d7 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -32,7 +32,7 @@

    Search documentation

    -
    +
    @@ -44,22 +44,22 @@ Pylons Website
  • - Narrative Documentation + Narrative Documentation
  • - API Documentation + API Documentation
  • - Tutorials + Tutorials
  • - Change History + Change History
  • - Sample Applications + Sample Applications
  • - Support and Development + Support and Development
  • IRC Channel -- cgit v1.2.3 From 8c03444dd5d80adaf65e30ebdabf1e6dde644965 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 23:32:50 -0400 Subject: prep for 1.1; gather contributors --- CHANGES.txt | 4 ++-- docs/authorintro.rst | 6 ++++-- docs/conf.py | 2 +- docs/copyright.rst | 8 ++++++-- setup.py | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 912c198bb..a2ad1f720 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -Next release -============ +1.1 (2011-07-22) +================ Features -------- diff --git a/docs/authorintro.rst b/docs/authorintro.rst index fb34f1d34..f1a9d1484 100644 --- a/docs/authorintro.rst +++ b/docs/authorintro.rst @@ -165,6 +165,7 @@ others' technology. single: Shipman, John single: Beelby, Chris single: Paez, Patricio + single: Merickel, Michael Thanks ====== @@ -179,8 +180,9 @@ software which it details would exist: Paul Everitt, Tres Seaver, Andrew Sawyers, Malthe Borch, Carlos de la Guardia, Chris Rossi, Shane Hathaway, Daniel Holth, Wichert Akkerman, Georg Brandl, Blaise Laflamme, Ben Bangert, Casey Duncan, Hugues Laflamme, Mike Orr, John Shipman, Chris Beelby, Patricio -Paez, Simon Oram, Nat Hardwick, Ian Bicking, Jim Fulton, Tom Moroz of the -Open Society Institute, and Todd Koym of Environmental Health Sciences. +Paez, Simon Oram, Nat Hardwick, Ian Bicking, Jim Fulton, Michael Merickel, +Tom Moroz of the Open Society Institute, and Todd Koym of Environmental +Health Sciences. Thanks to Guido van Rossum and Tim Peters for Python. diff --git a/docs/conf.py b/docs/conf.py index 597090c42..5eacb257f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.1b4' +version = '1.1' # The full version, including alpha/beta/rc tags. release = version diff --git a/docs/copyright.rst b/docs/copyright.rst index 1c7aa5844..4369a7a3f 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -1,7 +1,7 @@ Copyright, Trademarks, and Attributions ======================================= -*The Pyramid Web Application Development Framework, Version 1.0* +*The Pyramid Web Application Development Framework, Version 1.1* by Chris McDonough @@ -55,7 +55,11 @@ Contributors: Ben Bangert, Blaise Laflamme, Rob Miller, Mike Orr, Carlos de la Guardia, Paul Everitt, Tres Seaver, John Shipman, Marius Gedminas, Chris Rossi, Joachim Krebs, Xavier Spriet, Reed O'Brien, William Chambers, Charlie - Choiniere, Jamaludin Ahmad, Graham Higgins, Patricio Paez. + Choiniere, Jamaludin Ahmad, Graham Higgins, Patricio Paez, Michael + Merickel, Eric Ongerth, Niall O'Higgins, Christoph Zwerschke, John + Anderson, Atsushi Odagiri, Kirk Strauser, JD Navarro, Joe Dallago, + Savoir-Faire Linux, Łukasz Fidosz, Christopher Lambacher, Claus Conrad, + Chris Beelby, and a number of people with only psuedonyms on GitHub. Cover Designer: Hugues Laflamme of `Kemeneur `_. diff --git a/setup.py b/setup.py index 99bd761fc..376f503f6 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1b4', + version='1.1', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From 2519ab7cf207c529b0014927ff02179e2ffd3355 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 23:44:38 -0400 Subject: add epub and pdf maker scripts --- docs/make_epub | 2 ++ docs/make_pdf | 4 ++++ 2 files changed, 6 insertions(+) create mode 100755 docs/make_epub create mode 100755 docs/make_pdf diff --git a/docs/make_epub b/docs/make_epub new file mode 100755 index 000000000..868749e3b --- /dev/null +++ b/docs/make_epub @@ -0,0 +1,2 @@ +#!/bin/sh +make clean epub SPHINXBUILD=../env26/bin/sphinx-build diff --git a/docs/make_pdf b/docs/make_pdf new file mode 100755 index 000000000..558feb857 --- /dev/null +++ b/docs/make_pdf @@ -0,0 +1,4 @@ +#!/bin/sh +make clean latex SPHINXBUILD=../env26/bin/sphinx-build +cd _build/latex && make all-pdf + -- cgit v1.2.3 From 47a6bba2410a44da9df6e27f5927c7abacd44983 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 21 Jul 2011 23:48:03 -0400 Subject: better steps --- RELEASING.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASING.txt b/RELEASING.txt index c3781c19d..3116d2643 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -3,7 +3,11 @@ Releasing Pyramid - git pull -- Make sure all unit tests pass and statement coverage is at 100%:: +- Do platform test via tox: + + $ tox + +- Make sure statement coverage is at 100%:: $ python setup.py nosetests --with-coverage -- cgit v1.2.3 From 16b93e024e4c16e5684186fa34be2485de1e96ee Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 22 Jul 2011 07:02:10 -0400 Subject: s/repoze/pyramid/ --- docs/designdefense.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 9d3a3d38b..4975ae2a0 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1761,7 +1761,7 @@ If you can understand this hello world program, you can use Pyramid: Pyramid has ~ 650 pages of documentation (printed), covering topics from the very basic to the most advanced. *Nothing* is left undocumented, quite literally. It also has an *awesome*, very helpful community. Visit the -#repoze and/or #pylons IRC channels on freenode.net and see. +#pyramid and/or #pylons IRC channels on freenode.net and see. Hate Zope +++++++++ -- cgit v1.2.3 From 15ac15d60e788c087facfce85b1ead9f7dd4c3ae Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 22 Jul 2011 11:55:19 -0500 Subject: Fixed formatting fail. --- docs/narr/hooks.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 2a6414e1f..985934736 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -601,6 +601,9 @@ to make sure the object implements every attribute and method outlined in :class:`pyramid.interfaces.IResponse` and you'll have to ensure that it's marked up with ``zope.interface.implements(IResponse)``: +.. code-block:: python + :linenos: + from pyramid.interfaces import IResponse from zope.interface import implements -- cgit v1.2.3 From fa360e3b5dbc9866325a6a2c39279cb11f6b2918 Mon Sep 17 00:00:00 2001 From: Shane Hathaway Date: Fri, 22 Jul 2011 21:09:06 -0600 Subject: Signed contributor agreement --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index cdc011f87..4e9d944ea 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -142,3 +142,5 @@ Contributors - Michael Merickel, 2011/5/25 - Christoph Zwerschke, 2011/06/07 + +- Shane Hathaway, 2011/07/22 -- cgit v1.2.3 From 070bfdbaa6f3cffbda3a0bfe86534e24ea01142c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 23 Jul 2011 01:11:41 -0400 Subject: first cut --- pyramid/request.py | 14 ++++++++++++++ pyramid/router.py | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/pyramid/request.py b/pyramid/request.py index 8df204681..605b4b061 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -203,6 +203,7 @@ class Request(BaseRequest, DeprecatedRequestMethods): implements(IRequest) response_callbacks = () finished_callbacks = () + view_wrappers = () exception = None matchdict = None matched_route = None @@ -212,6 +213,19 @@ class Request(BaseRequest, DeprecatedRequestMethods): """ Template context (for Pylons apps) """ return TemplateContext() + def add_view_wrapper(self, wrapper): + """ Add a view wrapper """ + wrappers = self.view_wrappers + if not wrappers: + wrappers = [] + wrappers.append(wrapper) + self.view_wrappers = wrappers + + def _wrap_view(self, view, is_exc_view=False): + for wrapper in self.view_wrappers: + view = wrapper(view, is_exc_view) + return view + def add_response_callback(self, callback): """ Add a callback to the set of callbacks to be called by the diff --git a/pyramid/router.py b/pyramid/router.py index 0a92b5cd5..b11ebc7eb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -141,6 +141,11 @@ class Router(object): (IViewClassifier, request_iface, context_iface), IView, name=view_name, default=None) + # if there were any view wrappers set on the current + # request, use them to wrap the view + if request.view_wrappers: + view_callable = request._wrap_view(view_callable) + # invoke the view callable if view_callable is None: if self.debug_notfound: @@ -178,6 +183,9 @@ class Router(object): if view_callable is None: raise + if request.view_wrappers: + view_callable = request._wrap_view(view_callable, True) + response = view_callable(why, request) has_listeners and notify(NewResponse(request, response)) -- cgit v1.2.3 From acd256b2eb3304834ef0b87f499b1f85dbb014b7 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 00:08:40 -0700 Subject: fix typo --- docs/whatsnew-1.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 6761aaff8..c3161a010 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -152,7 +152,7 @@ to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. The environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and configuration -file value ``prevent_http_cache`` are synomymous and allow you to prevent +file value ``prevent_http_cache`` are synonymous and allow you to prevent HTTP cache headers from being set by Pyramid's ``http_cache`` machinery globally in a process. see :ref:`influencing_http_caching` and :ref:`preventing_http_caching`. -- cgit v1.2.3 From 9f164eeb0ccd77d47e22de5d57739452dfad3cbe Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 00:41:03 -0700 Subject: removed extra word --- docs/whatsnew-1.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index c3161a010..2a637136f 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -479,7 +479,7 @@ Deprecations and Behavior Differences these methods will be removed entirely. - A custom request factory is now required to return a request object that - has a ``response`` attribute (or "reified"/lazy property) if they the + has a ``response`` attribute (or "reified"/lazy property) if the request is meant to be used in a view that uses a renderer. This ``response`` attribute should be an instance of the class :class:`pyramid.response.Response`. -- cgit v1.2.3 From a37a8e4ec1c5b78387b8e93683792c74e8aedcca Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 01:01:34 -0700 Subject: Removed extra word, corrected unfinished sentence --- docs/whatsnew-1.1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 2a637136f..300b58dbf 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -542,8 +542,8 @@ Deprecations and Behavior Differences - Deprecated the :meth:`pyramid.config.Configurator.set_renderer_globals_factory` method and the ``renderer_globals`` Configurator constructor parameter. Users should - use convert code using this feature to use a BeforeRender event als - :ref:`beforerender_event`. + convert code using this feature to use a BeforeRender event. See the section + :ref:`beforerender_event` in the Hooks chapter. - In Pyramid 1.0, the :class:`pyramid.events.subscriber` directive behaved contrary to the documentation when passed more than one interface object to -- cgit v1.2.3 From a03b793abe9a9b5719593716c0191cdc0e9b741b Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 01:06:31 -0700 Subject: Fixed typo --- docs/whatsnew-1.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 300b58dbf..18d0aa0b1 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -586,7 +586,7 @@ Deprecations and Behavior Differences - The :meth:`pyramid.config.Configurator.add_route` method allowed two routes with the same route to be added without an intermediate call to - :meth:`pyramid.config.Configurator.commit``. If you now receive a + :meth:`pyramid.config.Configurator.commit`. If you now receive a ``ConfigurationError`` at startup time that appears to be ``add_route`` related, you'll need to either a) ensure that all of your route names are unique or b) call ``config.commit()`` before adding a second route with the -- cgit v1.2.3 From b202997e2e0b9eed860afc9baa9497ba5b986fb1 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 01:12:03 -0700 Subject: remove extra word --- docs/narr/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 6cc5a87e5..63c31d340 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -30,7 +30,7 @@ create web applications. own via a set of libraries if the framework provides a set of facilities that fits your application requirements. -Pyramid attempts to follow follow these design and engineering principles: +Pyramid attempts to follow these design and engineering principles: Simplicity :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get -- cgit v1.2.3 From 9a66aa09a8a773f67da59642a6642f1a3240c254 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 23 Jul 2011 13:40:24 -0400 Subject: - The ``pyramid.events.BeforeRender`` event now has an attribute named ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. --- CHANGES.txt | 10 ++++++++++ pyramid/events.py | 12 +++++++++--- pyramid/interfaces.py | 4 ++++ pyramid/renderers.py | 2 +- pyramid/tests/test_events.py | 9 +++++++-- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a2ad1f720..57ab76e46 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,13 @@ +Next release +============ + +Features +-------- + +- The ``pyramid.events.BeforeRender`` event now has an attribute named + ``rendering_val``. This can be used to introspect the value returned by a + view in a BeforeRender subscriber. + 1.1 (2011-07-22) ================ diff --git a/pyramid/events.py b/pyramid/events.py index b536bfd67..4021b94cc 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -193,10 +193,16 @@ class BeforeRender(dict): BeforeRender subscriber, your subscriber code will need to (using ``.get`` or ``__contains__`` of the event object) ensure no value already exists in the renderer globals dictionary before setting an overriding - value.""" + value. - def __init__(self, system): + The event has an additional attribute named ``rendering_val``. This is + the (non-system) value returned by a view or passed to ``render*`` as + ``value``. This feature is new in Pyramid 1.1.1. + """ + + def __init__(self, system, rendering_val=None): self._system = system + self.rendering_val = rendering_val def __setitem__(self, name, value): """ Set a name/value pair into the dictionary which is passed to a @@ -230,4 +236,4 @@ class BeforeRender(dict): """ Return the value for key ``k`` from the renderer globals dictionary, or the default if no such value exists.""" return self._system.get(k) - + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index d3a67e1aa..ec1d23acf 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -309,6 +309,10 @@ class IBeforeRender(Interface): """ Return the value for key ``k`` from the renderer globals dictionary, or the default if no such value exists.""" + rendering_val = Attribute('The value returned by a view or passed to a ' + '``render`` method for this rendering. ' + 'This feature is new in Pyramid 1.1.1.') + class IRenderer(Interface): def __call__(value, system): """ Call a the renderer implementation with the result of the diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 52459ee90..c718cf1bb 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -409,7 +409,7 @@ class RendererHelper(object): if renderer_globals: system_values.update(renderer_globals) - registry.notify(BeforeRender(system_values)) + registry.notify(BeforeRender(system_values, value)) result = renderer(value, system_values) return result diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index e3a10ccad..116a48bb7 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -179,9 +179,9 @@ class TestSubscriber(unittest.TestCase): [(foo, dec.register, 'pyramid')]) class TestBeforeRender(unittest.TestCase): - def _makeOne(self, system): + def _makeOne(self, system, val=None): from pyramid.events import BeforeRender - return BeforeRender(system) + return BeforeRender(system, val) def test_instance_conforms(self): from zope.interface.verify import verifyObject @@ -246,6 +246,11 @@ class TestBeforeRender(unittest.TestCase): event = self._makeOne(system) self.assertEqual(event.get('a'), None) + def test_rendering_val(self): + system = {} + val = {} + event = self._makeOne(system, val) + self.assertTrue(event.rendering_val is val) class DummyConfigurator(object): def __init__(self): -- cgit v1.2.3 From 5191e63deb8d3408eb5138aa00e5f9ef4e5e8e22 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 23 Jul 2011 16:04:29 -0400 Subject: dont wrap a view callable if it's None --- pyramid/request.py | 4 ++-- pyramid/router.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pyramid/request.py b/pyramid/request.py index 605b4b061..31a6bb12d 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -221,9 +221,9 @@ class Request(BaseRequest, DeprecatedRequestMethods): wrappers.append(wrapper) self.view_wrappers = wrappers - def _wrap_view(self, view, is_exc_view=False): + def _wrap_view(self, view, exc=None): for wrapper in self.view_wrappers: - view = wrapper(view, is_exc_view) + view = wrapper(view, self, exc) return view def add_response_callback(self, callback): diff --git a/pyramid/router.py b/pyramid/router.py index b11ebc7eb..0294d8d75 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -141,11 +141,6 @@ class Router(object): (IViewClassifier, request_iface, context_iface), IView, name=view_name, default=None) - # if there were any view wrappers set on the current - # request, use them to wrap the view - if request.view_wrappers: - view_callable = request._wrap_view(view_callable) - # invoke the view callable if view_callable is None: if self.debug_notfound: @@ -163,6 +158,11 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: + # if there were any view wrappers for the current + # request, use them to wrap the view + if request.view_wrappers: + view_callable = request._wrap_view(view_callable) + response = view_callable(context, request) # handle exceptions raised during root finding and view-exec @@ -184,7 +184,8 @@ class Router(object): raise if request.view_wrappers: - view_callable = request._wrap_view(view_callable, True) + view_callable = request._wrap_view(view_callable, + exc=why) response = view_callable(why, request) -- cgit v1.2.3 From b723792bfc43dc3d4446837c48d78c9258697e6d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 23 Jul 2011 19:57:34 -0400 Subject: - New method: ``pyramid.request.Request.add_view_mapper``. A view wrapper is used to wrap the found view callable before it is called by Pyramid's router. This is a feature usually only used by framework extensions, to provide, for example, view timing support. A view wrapper factory must be a callable which accepts three arguments: ``view_callable``, ``request``, and ``exc``. It must return a view callable. The view callable returned by the factory must implement the ``context, request`` view callable calling convention. For example:: import time def wrapper_factory(view_callable, request, exc): def wrapper(context, request): start = time.time() result = view_callable(context, request) end = time.time() request.view_timing = end - start return result return wrapper The ``view_callable`` argument to the factory will be the view callable found by Pyramid via view lookup. The ``request`` argument to the factory will be the current request. The ``exc`` argument to the factory will be an Exception object if the found view is an exception view; it will be ``None`` otherwise. View wrappers only last for the duration of a single request. You can add such a factory for every request by using the ``pyramid.events.NewRequest`` subscriber:: from pyramid.events import subscriber, NewRequest @subscriber(NewRequest) def newrequest(event): event.request.add_view_wrapper(wrapper_factory) If more than one view wrapper is registered during a single request, a 'later' view wrapper factory will be called with the result of its directly former view wrapper factory as its ``view_callable`` argument; this chain will be returned to Pyramid as a single view callable. --- CHANGES.txt | 44 +++++++++++++++++++++ docs/api/request.rst | 2 + pyramid/request.py | 50 +++++++++++++++++++++++- pyramid/tests/test_request.py | 30 ++++++++++++++ pyramid/tests/test_router.py | 91 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 216 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 57ab76e46..666b89b96 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,50 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. +- New method: ``pyramid.request.Request.add_view_mapper``. A view wrapper is + used to wrap the found view callable before it is called by Pyramid's + router. This is a feature usually only used by framework extensions, to + provide, for example, view timing support. + + A view wrapper factory must be a callable which accepts three arguments: + ``view_callable``, ``request``, and ``exc``. It must return a view + callable. The view callable returned by the factory must implement the + ``context, request`` view callable calling convention. For example:: + + import time + + def wrapper_factory(view_callable, request, exc): + def wrapper(context, request): + start = time.time() + result = view_callable(context, request) + end = time.time() + request.view_timing = end - start + return result + return wrapper + + The ``view_callable`` argument to the factory will be the view callable + found by Pyramid via view lookup. The ``request`` argument to the factory + will be the current request. The ``exc`` argument to the factory will be + an Exception object if the found view is an exception view; it will be + ``None`` otherwise. + + View wrappers only last for the duration of a single request. You can add + such a factory for every request by using the + ``pyramid.events.NewRequest`` subscriber:: + + from pyramid.events import subscriber, NewRequest + + @subscriber(NewRequest) + def newrequest(event): + event.request.add_view_wrapper(wrapper_factory) + + If more than one view wrapper is registered during a single request, + a 'later' view wrapper factory will be called with the result of its + directly former view wrapper factory as its ``view_callable`` + argument; this chain will be returned to Pyramid as a single view + callable. + + 1.1 (2011-07-22) ================ diff --git a/docs/api/request.rst b/docs/api/request.rst index 404825d1b..58532bbd1 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -154,6 +154,8 @@ .. automethod:: add_finished_callback + .. automethod:: add_view_wrapper + .. automethod:: route_url .. automethod:: route_path diff --git a/pyramid/request.py b/pyramid/request.py index 31a6bb12d..f84365dc5 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -214,7 +214,55 @@ class Request(BaseRequest, DeprecatedRequestMethods): return TemplateContext() def add_view_wrapper(self, wrapper): - """ Add a view wrapper """ + """ + Add a view wrapper factory. A view wrapper is used to wrap the found + view callable before it is called by Pyramid's router. This is a + feature usually only used by framework extensions, to provide, for + example, view timing support. + + A view wrapper factory must be a callable which accepts three + arguments: ``view_callable``, ``request``, and ``exc``. It must + return a view callable. The view callable returned by the factory + must implement the ``context, request`` view callable calling + convention. For example: + + .. code-block:: python + + import time + + def wrapper_factory(view_callable, request, exc): + def wrapper(context, request): + start = time.time() + result = view_callable(context, request) + end = time.time() + request.view_timing = end - start + return result + return wrapper + + The ``view_callable`` argument to the factory will be the view + callable found by Pyramid via :term:`view lookup`. The ``request`` + argument to the factory will be the current request. The ``exc`` + argument to the factory will be an Exception object if the found view + is a :term:`exception view`; it will be ``None`` otherwise. + + View wrappers only last for the duration of a single request. You + can add such a factory for every request by using the + :class:`pyramid.events.NewRequest` subscriber: + + .. code-block:: python + + from pyramid.events import subscriber, NewRequest + + @subscriber(NewRequest) + def newrequest(event): + event.request.add_view_wrapper(wrapper_factory) + + If more than one view wrapper is registered during a single request, + a 'later' view wrapper factory will be called with the result of its + directly former view wrapper factory as its ``view_callable`` + argument; this chain will be returned to Pyramid as a single view + callable. + """ wrappers = self.view_wrappers if not wrappers: wrappers = [] diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 066aa9207..3c24c8f58 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -145,6 +145,36 @@ class TestRequest(unittest.TestCase): self.assertEqual(inst.called2, True) self.assertEqual(inst.finished_callbacks, []) + def test_add_view_wrapper(self): + inst = self._makeOne({}) + wrapper = object() + inst.add_view_wrapper(wrapper) + self.assertEqual(inst.view_wrappers, [wrapper]) + + def test_add_view_wrapper_wrappers_exist(self): + inst = self._makeOne({}) + inst.view_wrappers = [123] + wrapper = object() + inst.add_view_wrapper(wrapper) + self.assertEqual(inst.view_wrappers, [123, wrapper]) + + def test__wrap_view_no_wrappers(self): + inst = self._makeOne({}) + wrapped = inst._wrap_view(lambda *arg: 'OK') + self.assertEqual(wrapped(), 'OK') + + def test__wrap_view_with_wrappers_no_exc(self): + inst = self._makeOne({}) + def view(*arg): return 'OK' + def view_wrapper(_view, request, exc): + self.assertEqual(_view, view) + self.assertEqual(request, inst) + self.assertEqual(exc, '123') + return _view + inst.view_wrappers = [view_wrapper] + wrapped = inst._wrap_view(view, exc='123') + self.assertEqual(wrapped(), 'OK') + def test_resource_url(self): self._registerContextURL() inst = self._makeOne({}) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 55eed50f5..1a70ca5a3 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -498,6 +498,97 @@ class TestRouter(unittest.TestCase): exc_raised(NotImplementedError, router, environ, start_response) self.assertEqual(environ['called_back'], True) + def test_call_request_has_view_wrappers(self): + from zope.interface import Interface + from zope.interface import directlyProvides + class IContext(Interface): + pass + from pyramid.interfaces import IRequest + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import INewRequest + wrappers = [] + class ViewWrapper(object): + def __call__(self, view, request, exc): + self.view = view + self.exc = exc + wrappers.append(self) + return self.wrap + def wrap(self, context, request): + return self.view(context, request) + wrapper1 = ViewWrapper() + wrapper2 = ViewWrapper() + def newrequest(event): + event.request.view_wrappers = [wrapper1, wrapper2] + self.registry.registerHandler(newrequest, (INewRequest,)) + context = DummyContext() + directlyProvides(context, IContext) + self._registerTraverserFactory(context, subpath=['']) + response = DummyResponse('200 OK') + response.app_iter = ['OK'] + def view(context, request): + return response + environ = self._makeEnviron() + self._registerView(view, '', IViewClassifier, IRequest, IContext) + router = self._makeOne() + start_response = DummyStartResponse() + itera = router(environ, start_response) + wrapper1, wrapper2 = wrappers + self.assertEqual(wrapper1.view, view) + self.assertEqual(wrapper2.view, wrapper1.wrap) + self.assertEqual(wrapper1.exc, None) + self.assertEqual(wrapper2.exc, None) + self.assertEqual(itera, ['OK']) + + def test_call_request_has_view_wrappers_in_exception(self): + from zope.interface import Interface + from zope.interface import directlyProvides + class IContext(Interface): + pass + from pyramid.interfaces import IRequest + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import INewRequest + from pyramid.interfaces import IExceptionViewClassifier + wrappers = [] + class ViewWrapper(object): + def __init__(self): + self.views = [] + self.exc = [] + def __call__(self, view, request, exc): + self.views.append(view) + self.exc.append(exc) + wrappers.append(self) + return self.wrap + def wrap(self, context, request): + return self.views[-1](context, request) + wrapper1 = ViewWrapper() + wrapper2 = ViewWrapper() + def newrequest(event): + event.request.view_wrappers = [wrapper1, wrapper2] + self.registry.registerHandler(newrequest, (INewRequest,)) + context = DummyContext() + directlyProvides(context, IContext) + self._registerTraverserFactory(context, subpath=['']) + error = NotImplementedError() + def view(context, request): + raise error + environ = self._makeEnviron() + self._registerView(view, '', IViewClassifier, IRequest, IContext) + exception_response = DummyResponse('404 Not Found') + exception_response.app_iter = ['Not Found'] + exception_view = DummyView(exception_response) + environ = self._makeEnviron() + self._registerView(exception_view, '', IExceptionViewClassifier, + IRequest, NotImplementedError) + router = self._makeOne() + start_response = DummyStartResponse() + itera = router(environ, start_response) + wrapper1, wrapper2, wrapper3, wrapper4 = wrappers + self.assertEqual(wrapper1.views, [view, exception_view]) + self.assertEqual(wrapper2.views, [wrapper1.wrap, wrapper1.wrap]) + self.assertEqual(wrapper1.exc, [None, error]) + self.assertEqual(wrapper2.exc, [None, error]) + self.assertEqual(itera, ['Not Found']) + def test_call_request_factory_raises(self): # making sure finally doesnt barf when a request cannot be created environ = self._makeEnviron() -- cgit v1.2.3 From ac77e365c8269953a5dde89157d7468529ee7432 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 23 Jul 2011 19:58:55 -0400 Subject: typo --- CHANGES.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 666b89b96..3c232d880 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,8 +8,8 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. -- New method: ``pyramid.request.Request.add_view_mapper``. A view wrapper is - used to wrap the found view callable before it is called by Pyramid's +- New method: ``pyramid.request.Request.add_view_wrapper``. A view wrapper + is used to wrap the found view callable before it is called by Pyramid's router. This is a feature usually only used by framework extensions, to provide, for example, view timing support. -- cgit v1.2.3 From af2323bcb169653dd2d76d7c40909fd881041beb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 01:01:38 -0400 Subject: first cut --- CHANGES.txt | 59 +++++++++++ docs/api/config.rst | 2 + pyramid/config.py | 81 ++++++++++++++ pyramid/interfaces.py | 18 ++++ pyramid/request.py | 2 + pyramid/router.py | 284 +++++++++++++++++++++++++++----------------------- 6 files changed, 318 insertions(+), 128 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3c232d880..b1979347a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -51,6 +51,65 @@ Features argument; this chain will be returned to Pyramid as a single view callable. +- New configurator directive: + ``pyramid.config.Configurator.add_request_handler``. This directive adds + a request handler factory. + + A request handler factory is used to wrap the Pyramid router's primary + request handling function. This is a feature usually only used by + framework extensions, to provide, for example, view timing support and as + a convenient place to hang bookkeeping code that examines exceptions + before they are returned to the server. + + A request handler factory (passed as ``handler_factory``) must be a + callable which accepts two arguments: ``handler`` and ``registry``. + ``handler`` will be the request handler being wrapped. ``registry`` will + be the Pyramid application registry represented by this Configurator. A + request handler factory must return a request handler when it is called. + + A request handler accepts a request object and returns a response object. + + Here's an example of creating both a handler factory and a handler, and + registering the handler factory: + + .. code-block:: python + + import time + + def timing_handler_factory(handler, registry): + if registry.settings['do_timing']: + # if timing support is enabled, return a wrapper + def timing_handler(request): + start = time.time() + try: + response = handler(request) + finally: + end = time.time() + print: 'The request took %s seconds' % (end - start) + return response + return timing_handler + # if timing support is not enabled, return the original handler + return handler + + config.add_request_handler(timing_handler_factory, 'timing') + + The ``request`` argument to the handler will be the request created by + Pyramid's router when it receives a WSGI request. + + If more than one request handler factory is registered into a single + configuration, the request handlers will be chained together. The first + request handler factory added (in code execution order) will be called + with the default Pyramid request handler, the second handler factory added + will be called with the result of the first handler factory, ad + infinitum. The Pyramid router will use the outermost wrapper in this chain + (which is a bit like a WSGI middleware "pipeline") as its handler + function. + + The ``name`` argument to this function is required. The name is used as a + key for conflict detection. No two request handler factories may share + the same name in the same configuration (unless + automatic_conflict_resolution is able to resolve the conflict or + this is an autocommitting configurator). 1.1 (2011-07-22) ================ diff --git a/docs/api/config.rst b/docs/api/config.rst index 96e955388..21e2b828d 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -78,6 +78,8 @@ .. automethod:: set_view_mapper + .. automethod:: add_request_handler + .. automethod:: testing_securitypolicy .. automethod:: testing_resources diff --git a/pyramid/config.py b/pyramid/config.py index cad853674..6c47e7871 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -37,6 +37,8 @@ from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IRequestFactory +from pyramid.interfaces import IRequestHandlerFactory +from pyramid.interfaces import IRequestHandlerFactories from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -885,6 +887,85 @@ class Configurator(object): :term:`response` object. """ return self._derive_view(view, attr=attr, renderer=renderer) + @action_method + def add_request_handler(self, handler_factory, name): + """ + Add a request handler factory. A request handler factory is used to + wrap the Pyramid router's primary request handling function. This is + a feature usually only used by framework extensions, to provide, for + example, view timing support and as a convenient place to hang + bookkeeping code that examines exceptions before they are returned to + the server. + + A request handler factory (passed as ``handler_factory``) must be a + callable which accepts two arguments: ``handler`` and ``registry``. + ``handler`` will be the request handler being wrapped. ``registry`` + will be the Pyramid :term:`application registry` represented by this + Configurator. A request handler factory must return a request + handler when it is called. + + A request handler accepts a :term:`request` object and returns a + :term:`response` object. + + Here's an example of creating both a handler factory and a handler, + and registering the handler factory: + + .. code-block:: python + + import time + + def timing_handler_factory(handler, registry): + if registry.settings['do_timing']: + # if timing support is enabled, return a wrapper + def timing_handler(request): + start = time.time() + try: + response = handler(request) + finally: + end = time.time() + print: 'The request took %s seconds' % (end - start) + return response + return timing_handler + # if timing support is not enabled, return the original handler + return handler + + config.add_request_handler(timing_handler_factory, 'timing') + + The ``request`` argument to the handler will be the request created + by Pyramid's router when it receives a WSGI request. + + If more than one request handler factory is registered into a single + configuration, the request handlers will be chained together. The + first request handler factory added (in code execution order) will be + called with the default Pyramid request handler, the second handler + factory added will be called with the result of the first handler + factory, ad infinitum. The Pyramid router will use the outermost + wrapper in this chain (which is a bit like a WSGI middleware + "pipeline") as its handler function. + + The ``name`` argument to this function is required. The name is used + as a key for conflict detection. No two request handler factories + may share the same name in the same configuration (unless + :ref:`automatic_conflict_resolution` is able to resolve the conflict + or this is an autocommitting configurator). + + .. note:: This feature is new as of Pyramid 1.1.1. + """ + def register(): + registry = self.registry + existing_factory = registry.queryUtility(IRequestHandlerFactory, + name=name) + registry.registerUtility(handler_factory, IRequestHandlerFactory, + name=name) + existing_names = registry.queryUtility(IRequestHandlerFactories, + default=[]) + if not existing_factory: + # don't replace a name if someone is trying to override + # through a commit + existing_names.append(name) + registry.registerUtility(existing_names, IRequestHandlerFactories) + self.action(('requesthandler', name), register) + @action_method def add_subscriber(self, subscriber, iface=None): """Add an event :term:`subscriber` for the event stream diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index ec1d23acf..a06cb7e52 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -446,6 +446,24 @@ class IMultiDict(Interface): # docs-only interface class IRequest(Interface): """ Request type interface attached to all request objects """ +class IRequestHandlerFactories(Interface): + """ Marker interface for utility registration representing the ordered + set of a configuration's request handler factories""" + +class IRequestHandlerFactory(Interface): + """ A request handler factory can be used to augment Pyramid's default + mainloop request handling.""" + def __call__(self, handler, registry): + """ Return an IRequestHandler; the ``handler`` argument passed will + be the previous request handler added, or the default request handler + if no request handlers have yet been added .""" + +class IRequestHandler(Interface): + """ """ + def __call__(self, request): + """ Must return an IResponse or raise an exception. The ``request`` + argument will be an instance of an object that provides IRequest.""" + IRequest.combined = IRequest # for exception view lookups class IRouteRequest(Interface): diff --git a/pyramid/request.py b/pyramid/request.py index f84365dc5..927319479 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -262,6 +262,8 @@ class Request(BaseRequest, DeprecatedRequestMethods): directly former view wrapper factory as its ``view_callable`` argument; this chain will be returned to Pyramid as a single view callable. + + .. note:: This feature is new as of Pyramid 1.1.1. """ wrappers = self.view_wrappers if not wrappers: diff --git a/pyramid/router.py b/pyramid/router.py index 0294d8d75..dcf257beb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -12,6 +12,8 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier +from pyramid.interfaces import IRequestHandlerFactory +from pyramid.interfaces import IRequestHandlerFactories from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -36,6 +38,14 @@ class Router(object): self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) + handler_factory_names = q(IRequestHandlerFactories) + handler = self.handle_request + if handler_factory_names: + for name in handler_factory_names: + handler_factory = registry.getUtility(IRequestHandlerFactory, + name=name) + handler = handler_factory(handler, registry) + self.handle_request = handler self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings @@ -44,6 +54,150 @@ class Router(object): self.debug_notfound = settings['debug_notfound'] self.debug_routematch = settings['debug_routematch'] + def handle_request(self, request): + attrs = request.__dict__ + registry = attrs['registry'] + request_iface = IRequest + context = None + routes_mapper = self.routes_mapper + debug_routematch = self.debug_routematch + adapters = registry.adapters + has_listeners = registry.has_listeners + notify = registry.notify + logger = self.logger + try: # matches except Exception (exception view execution) + has_listeners and notify(NewRequest(request)) + # find the root object + root_factory = self.root_factory + if routes_mapper is not None: + info = routes_mapper(request) + match, route = info['match'], info['route'] + if route is None: + if debug_routematch: + msg = ('no route matched for url %s' % + request.url) + logger and logger.debug(msg) + else: + # TODO: kill off bfg.routes.* environ keys + # when traverser requires request arg, and + # cant cope with environ anymore (they are + # docs-deprecated as of BFG 1.3) + environ = request.environ + environ['bfg.routes.route'] = route + environ['bfg.routes.matchdict'] = match + attrs['matchdict'] = match + attrs['matched_route'] = route + + if debug_routematch: + msg = ( + 'route matched for url %s; ' + 'route_name: %r, ' + 'path_info: %r, ' + 'pattern: %r, ' + 'matchdict: %r, ' + 'predicates: %r' % ( + request.url, + route.name, + request.path_info, + route.pattern, match, + route.predicates) + ) + logger and logger.debug(msg) + + request_iface = registry.queryUtility( + IRouteRequest, + name=route.name, + default=IRequest) + + root_factory = route.factory or \ + self.root_factory + + root = root_factory(request) + attrs['root'] = root + + # find a context + traverser = adapters.queryAdapter(root, ITraverser) + if traverser is None: + traverser = ResourceTreeTraverser(root) + tdict = traverser(request) + + context, view_name, subpath, traversed, vroot, \ + vroot_path = ( + tdict['context'], + tdict['view_name'], + tdict['subpath'], + tdict['traversed'], + tdict['virtual_root'], + tdict['virtual_root_path'] + ) + + attrs.update(tdict) + has_listeners and notify(ContextFound(request)) + + # find a view callable + context_iface = providedBy(context) + view_callable = adapters.lookup( + (IViewClassifier, request_iface, context_iface), + IView, name=view_name, default=None) + + # invoke the view callable + if view_callable is None: + if self.debug_notfound: + msg = ( + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, + subpath, traversed, root, vroot, + vroot_path) + ) + logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) + else: + # if there were any view wrappers for the current + # request, use them to wrap the view + if request.view_wrappers: + view_callable = request._wrap_view( + view_callable) + + response = view_callable(context, request) + + # handle exceptions raised during root finding and view-exec + except Exception, why: + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] + + attrs['exception'] = why + + for_ = (IExceptionViewClassifier, + request_iface.combined, + providedBy(why)) + view_callable = adapters.lookup(for_, IView, + default=None) + + if view_callable is None: + raise + + if request.view_wrappers: + view_callable = request._wrap_view(view_callable, + exc=why) + + response = view_callable(why, request) + + has_listeners and notify(NewResponse(request, response)) + + if request.response_callbacks: + request._process_response_callbacks(response) + + return response + def __call__(self, environ, start_response): """ Accept ``environ`` and ``start_response``; create a @@ -53,13 +207,7 @@ class Router(object): return an iterable. """ registry = self.registry - adapters = registry.adapters - has_listeners = registry.has_listeners - notify = registry.notify - logger = self.logger manager = self.threadlocal_manager - routes_mapper = self.routes_mapper - debug_routematch = self.debug_routematch request = None threadlocals = {'registry':registry, 'request':request} manager.push(threadlocals) @@ -70,129 +218,9 @@ class Router(object): # create the request request = self.request_factory(environ) - context = None threadlocals['request'] = request - attrs = request.__dict__ - attrs['registry'] = registry - request_iface = IRequest - - try: # matches except Exception (exception view execution) - has_listeners and notify(NewRequest(request)) - # find the root object - root_factory = self.root_factory - if routes_mapper is not None: - info = routes_mapper(request) - match, route = info['match'], info['route'] - if route is None: - if debug_routematch: - msg = ('no route matched for url %s' % - request.url) - logger and logger.debug(msg) - else: - # TODO: kill off bfg.routes.* environ keys when - # traverser requires request arg, and cant cope - # with environ anymore (they are docs-deprecated as - # of BFG 1.3) - environ['bfg.routes.route'] = route - environ['bfg.routes.matchdict'] = match - attrs['matchdict'] = match - attrs['matched_route'] = route - - if debug_routematch: - msg = ( - 'route matched for url %s; ' - 'route_name: %r, ' - 'path_info: %r, ' - 'pattern: %r, ' - 'matchdict: %r, ' - 'predicates: %r' % ( - request.url, - route.name, - request.path_info, - route.pattern, match, - route.predicates) - ) - logger and logger.debug(msg) - - request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) - root_factory = route.factory or self.root_factory - - root = root_factory(request) - attrs['root'] = root - - # find a context - traverser = adapters.queryAdapter(root, ITraverser) - if traverser is None: - traverser = ResourceTreeTraverser(root) - tdict = traverser(request) - context, view_name, subpath, traversed, vroot, vroot_path =( - tdict['context'], tdict['view_name'], tdict['subpath'], - tdict['traversed'], tdict['virtual_root'], - tdict['virtual_root_path']) - attrs.update(tdict) - has_listeners and notify(ContextFound(request)) - - # find a view callable - context_iface = providedBy(context) - view_callable = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name=view_name, default=None) - - # invoke the view callable - if view_callable is None: - if self.debug_notfound: - msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, - subpath, traversed, root, vroot, vroot_path) - ) - logger and logger.debug(msg) - else: - msg = request.path_info - raise HTTPNotFound(msg) - else: - # if there were any view wrappers for the current - # request, use them to wrap the view - if request.view_wrappers: - view_callable = request._wrap_view(view_callable) - - response = view_callable(context, request) - - # handle exceptions raised during root finding and view-exec - except Exception, why: - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] - - attrs['exception'] = why - - for_ = (IExceptionViewClassifier, - request_iface.combined, - providedBy(why)) - view_callable = adapters.lookup(for_, IView, default=None) - - if view_callable is None: - raise - - if request.view_wrappers: - view_callable = request._wrap_view(view_callable, - exc=why) - - response = view_callable(why, request) - - has_listeners and notify(NewResponse(request, response)) - - if request.response_callbacks: - request._process_response_callbacks(response) + request.registry = registry + response = self.handle_request(request) finally: if request is not None and request.finished_callbacks: -- cgit v1.2.3 From 6434998267a4930fd9175df06b9a07d83937b71d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 01:05:56 -0400 Subject: back this feature out; we'll try a different approach --- CHANGES.txt | 44 --------------------- docs/api/request.rst | 2 - pyramid/request.py | 62 ----------------------------- pyramid/router.py | 9 ----- pyramid/tests/test_request.py | 30 -------------- pyramid/tests/test_router.py | 91 ------------------------------------------- 6 files changed, 238 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3c232d880..57ab76e46 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,50 +8,6 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. -- New method: ``pyramid.request.Request.add_view_wrapper``. A view wrapper - is used to wrap the found view callable before it is called by Pyramid's - router. This is a feature usually only used by framework extensions, to - provide, for example, view timing support. - - A view wrapper factory must be a callable which accepts three arguments: - ``view_callable``, ``request``, and ``exc``. It must return a view - callable. The view callable returned by the factory must implement the - ``context, request`` view callable calling convention. For example:: - - import time - - def wrapper_factory(view_callable, request, exc): - def wrapper(context, request): - start = time.time() - result = view_callable(context, request) - end = time.time() - request.view_timing = end - start - return result - return wrapper - - The ``view_callable`` argument to the factory will be the view callable - found by Pyramid via view lookup. The ``request`` argument to the factory - will be the current request. The ``exc`` argument to the factory will be - an Exception object if the found view is an exception view; it will be - ``None`` otherwise. - - View wrappers only last for the duration of a single request. You can add - such a factory for every request by using the - ``pyramid.events.NewRequest`` subscriber:: - - from pyramid.events import subscriber, NewRequest - - @subscriber(NewRequest) - def newrequest(event): - event.request.add_view_wrapper(wrapper_factory) - - If more than one view wrapper is registered during a single request, - a 'later' view wrapper factory will be called with the result of its - directly former view wrapper factory as its ``view_callable`` - argument; this chain will be returned to Pyramid as a single view - callable. - - 1.1 (2011-07-22) ================ diff --git a/docs/api/request.rst b/docs/api/request.rst index 58532bbd1..404825d1b 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -154,8 +154,6 @@ .. automethod:: add_finished_callback - .. automethod:: add_view_wrapper - .. automethod:: route_url .. automethod:: route_path diff --git a/pyramid/request.py b/pyramid/request.py index f84365dc5..8df204681 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -203,7 +203,6 @@ class Request(BaseRequest, DeprecatedRequestMethods): implements(IRequest) response_callbacks = () finished_callbacks = () - view_wrappers = () exception = None matchdict = None matched_route = None @@ -213,67 +212,6 @@ class Request(BaseRequest, DeprecatedRequestMethods): """ Template context (for Pylons apps) """ return TemplateContext() - def add_view_wrapper(self, wrapper): - """ - Add a view wrapper factory. A view wrapper is used to wrap the found - view callable before it is called by Pyramid's router. This is a - feature usually only used by framework extensions, to provide, for - example, view timing support. - - A view wrapper factory must be a callable which accepts three - arguments: ``view_callable``, ``request``, and ``exc``. It must - return a view callable. The view callable returned by the factory - must implement the ``context, request`` view callable calling - convention. For example: - - .. code-block:: python - - import time - - def wrapper_factory(view_callable, request, exc): - def wrapper(context, request): - start = time.time() - result = view_callable(context, request) - end = time.time() - request.view_timing = end - start - return result - return wrapper - - The ``view_callable`` argument to the factory will be the view - callable found by Pyramid via :term:`view lookup`. The ``request`` - argument to the factory will be the current request. The ``exc`` - argument to the factory will be an Exception object if the found view - is a :term:`exception view`; it will be ``None`` otherwise. - - View wrappers only last for the duration of a single request. You - can add such a factory for every request by using the - :class:`pyramid.events.NewRequest` subscriber: - - .. code-block:: python - - from pyramid.events import subscriber, NewRequest - - @subscriber(NewRequest) - def newrequest(event): - event.request.add_view_wrapper(wrapper_factory) - - If more than one view wrapper is registered during a single request, - a 'later' view wrapper factory will be called with the result of its - directly former view wrapper factory as its ``view_callable`` - argument; this chain will be returned to Pyramid as a single view - callable. - """ - wrappers = self.view_wrappers - if not wrappers: - wrappers = [] - wrappers.append(wrapper) - self.view_wrappers = wrappers - - def _wrap_view(self, view, exc=None): - for wrapper in self.view_wrappers: - view = wrapper(view, self, exc) - return view - def add_response_callback(self, callback): """ Add a callback to the set of callbacks to be called by the diff --git a/pyramid/router.py b/pyramid/router.py index 0294d8d75..0a92b5cd5 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -158,11 +158,6 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: - # if there were any view wrappers for the current - # request, use them to wrap the view - if request.view_wrappers: - view_callable = request._wrap_view(view_callable) - response = view_callable(context, request) # handle exceptions raised during root finding and view-exec @@ -183,10 +178,6 @@ class Router(object): if view_callable is None: raise - if request.view_wrappers: - view_callable = request._wrap_view(view_callable, - exc=why) - response = view_callable(why, request) has_listeners and notify(NewResponse(request, response)) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 3c24c8f58..066aa9207 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -145,36 +145,6 @@ class TestRequest(unittest.TestCase): self.assertEqual(inst.called2, True) self.assertEqual(inst.finished_callbacks, []) - def test_add_view_wrapper(self): - inst = self._makeOne({}) - wrapper = object() - inst.add_view_wrapper(wrapper) - self.assertEqual(inst.view_wrappers, [wrapper]) - - def test_add_view_wrapper_wrappers_exist(self): - inst = self._makeOne({}) - inst.view_wrappers = [123] - wrapper = object() - inst.add_view_wrapper(wrapper) - self.assertEqual(inst.view_wrappers, [123, wrapper]) - - def test__wrap_view_no_wrappers(self): - inst = self._makeOne({}) - wrapped = inst._wrap_view(lambda *arg: 'OK') - self.assertEqual(wrapped(), 'OK') - - def test__wrap_view_with_wrappers_no_exc(self): - inst = self._makeOne({}) - def view(*arg): return 'OK' - def view_wrapper(_view, request, exc): - self.assertEqual(_view, view) - self.assertEqual(request, inst) - self.assertEqual(exc, '123') - return _view - inst.view_wrappers = [view_wrapper] - wrapped = inst._wrap_view(view, exc='123') - self.assertEqual(wrapped(), 'OK') - def test_resource_url(self): self._registerContextURL() inst = self._makeOne({}) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 1a70ca5a3..55eed50f5 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -498,97 +498,6 @@ class TestRouter(unittest.TestCase): exc_raised(NotImplementedError, router, environ, start_response) self.assertEqual(environ['called_back'], True) - def test_call_request_has_view_wrappers(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from pyramid.interfaces import IRequest - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import INewRequest - wrappers = [] - class ViewWrapper(object): - def __call__(self, view, request, exc): - self.view = view - self.exc = exc - wrappers.append(self) - return self.wrap - def wrap(self, context, request): - return self.view(context, request) - wrapper1 = ViewWrapper() - wrapper2 = ViewWrapper() - def newrequest(event): - event.request.view_wrappers = [wrapper1, wrapper2] - self.registry.registerHandler(newrequest, (INewRequest,)) - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - response = DummyResponse('200 OK') - response.app_iter = ['OK'] - def view(context, request): - return response - environ = self._makeEnviron() - self._registerView(view, '', IViewClassifier, IRequest, IContext) - router = self._makeOne() - start_response = DummyStartResponse() - itera = router(environ, start_response) - wrapper1, wrapper2 = wrappers - self.assertEqual(wrapper1.view, view) - self.assertEqual(wrapper2.view, wrapper1.wrap) - self.assertEqual(wrapper1.exc, None) - self.assertEqual(wrapper2.exc, None) - self.assertEqual(itera, ['OK']) - - def test_call_request_has_view_wrappers_in_exception(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from pyramid.interfaces import IRequest - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import INewRequest - from pyramid.interfaces import IExceptionViewClassifier - wrappers = [] - class ViewWrapper(object): - def __init__(self): - self.views = [] - self.exc = [] - def __call__(self, view, request, exc): - self.views.append(view) - self.exc.append(exc) - wrappers.append(self) - return self.wrap - def wrap(self, context, request): - return self.views[-1](context, request) - wrapper1 = ViewWrapper() - wrapper2 = ViewWrapper() - def newrequest(event): - event.request.view_wrappers = [wrapper1, wrapper2] - self.registry.registerHandler(newrequest, (INewRequest,)) - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - error = NotImplementedError() - def view(context, request): - raise error - environ = self._makeEnviron() - self._registerView(view, '', IViewClassifier, IRequest, IContext) - exception_response = DummyResponse('404 Not Found') - exception_response.app_iter = ['Not Found'] - exception_view = DummyView(exception_response) - environ = self._makeEnviron() - self._registerView(exception_view, '', IExceptionViewClassifier, - IRequest, NotImplementedError) - router = self._makeOne() - start_response = DummyStartResponse() - itera = router(environ, start_response) - wrapper1, wrapper2, wrapper3, wrapper4 = wrappers - self.assertEqual(wrapper1.views, [view, exception_view]) - self.assertEqual(wrapper2.views, [wrapper1.wrap, wrapper1.wrap]) - self.assertEqual(wrapper1.exc, [None, error]) - self.assertEqual(wrapper2.exc, [None, error]) - self.assertEqual(itera, ['Not Found']) - def test_call_request_factory_raises(self): # making sure finally doesnt barf when a request cannot be created environ = self._makeEnviron() -- cgit v1.2.3 From afe76b3bc0f378030d2dbffeb63faba829139f8f Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 22:11:31 -0700 Subject: remove extra word --- docs/narr/firstapp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 6c53f3de0..eb05f8e6d 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -153,7 +153,7 @@ defined imports and function definitions, placed within the confines of an app = config.make_wsgi_app() serve(app, host='0.0.0.0') -Let's break this down this piece-by-piece. +Let's break this down piece-by-piece. Configurator Construction ~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 84bd9544083c38ded264b58f13958ab945c92e5e Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 22:29:03 -0700 Subject: typo --- docs/narr/firstapp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index eb05f8e6d..66632e123 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -182,7 +182,7 @@ The ``config = Configurator()`` line above creates an instance of the :class:`~pyramid.config.Configurator` class. The resulting ``config`` object represents an API which the script uses to configure this particular :app:`Pyramid` application. Methods called on the Configurator will cause -registrations to be made in a :term:`application registry` associated with +registrations to be made in an :term:`application registry` associated with the application. .. _adding_configuration: -- cgit v1.2.3 From dc45abfdfe1ec247ce199dcedeb178146a749023 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 01:34:33 -0400 Subject: add tests for request handler bits --- pyramid/tests/test_config.py | 31 ++++++++++++++++++++++++++++ pyramid/tests/test_router.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 88c39f36d..630fe43ce 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -601,6 +601,37 @@ class ConfiguratorTests(unittest.TestCase): settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) + def test_add_request_handlers_names_distinct(self): + from pyramid.interfaces import IRequestHandlerFactories + from pyramid.interfaces import IRequestHandlerFactory + def factory1(handler, registry): return handler + def factory2(handler, registry): return handler + config = self._makeOne() + config.add_request_handler(factory1, 'name1') + config.add_request_handler(factory2, 'name2') + config.commit() + names = config.registry.queryUtility(IRequestHandlerFactories) + self.assertEqual(names, ['name1', 'name2']) + f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + f2 = config.registry.getUtility(IRequestHandlerFactory, name='name2') + self.assertEqual(f1, factory1) + self.assertEqual(f2, factory2) + + def test_add_request_handlers_names_overlap(self): + from pyramid.interfaces import IRequestHandlerFactories + from pyramid.interfaces import IRequestHandlerFactory + def factory1(handler, registry): return handler + def factory2(handler, registry): return handler + def factory3(handler, registry): return handler + config = self._makeOne(autocommit=True) + config.add_request_handler(factory1, 'name1') + config.add_request_handler(factory2, 'name2') + config.add_request_handler(factory3, 'name1') + names = config.registry.queryUtility(IRequestHandlerFactories) + self.assertEqual(names, ['name1', 'name2']) + f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + self.assertEqual(f3, factory3) + def test_add_subscriber_defaults(self): from zope.interface import implements from zope.interface import Interface diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 1a70ca5a3..a1e7252e9 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -134,6 +134,54 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.request_factory, DummyRequestFactory) + def test_request_handler_factories(self): + from pyramid.interfaces import IRequestHandlerFactory + from pyramid.interfaces import IRequestHandlerFactories + L = [] + def handler_factory1(handler, registry): + L.append((handler, registry)) + def wrapper(request): + request.environ['handled'].append('one') + return handler(request) + wrapper.name = 'one' + wrapper.child = handler + return wrapper + def handler_factory2(handler, registry): + L.append((handler, registry)) + def wrapper(request): + request.environ['handled'] = ['two'] + return handler(request) + wrapper.name = 'two' + wrapper.child = handler + return wrapper + self.registry.registerUtility(['one', 'two'], IRequestHandlerFactories) + self.registry.registerUtility(handler_factory1, + IRequestHandlerFactory, name='one') + self.registry.registerUtility(handler_factory2, + IRequestHandlerFactory, name='two') + router = self._makeOne() + self.assertEqual(router.handle_request.name, 'two') + self.assertEqual(router.handle_request.child.name, 'one') + self.assertEqual(router.handle_request.child.child.__name__, + 'handle_request') + from pyramid.response import Response + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IResponse + context = DummyContext() + self._registerTraverserFactory(context) + environ = self._makeEnviron() + view = DummyView('abc') + self._registerView(self.config.derive_view(view), '', + IViewClassifier, None, None) + start_response = DummyStartResponse() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + app_iter = router(environ, start_response) + self.assertEqual(app_iter, ['abc']) + self.assertEqual(start_response.status, '200 OK') + self.assertEqual(environ['handled'], ['two', 'one']) + def test_call_traverser_default(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() -- cgit v1.2.3 From 3a80546633185b64390b7779bab459a7e58f22c4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 02:08:51 -0400 Subject: - The Pyramid debug logger now uses the standard logging configuration (usually set up by Paste as part of startup). This means that output from e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the normal logging channels. The logger name of the debug logger will be the package name of the *caller* of the Configurator's constructor. - If a string is passed as the ``debug_logger`` parameter to a Configurator, that string is considered to be the name of a global Python logger rather than a dotted name to an instance of a logger. --- CHANGES.txt | 15 ++++++++++++++- pyramid/config.py | 28 +++++++++++++--------------- pyramid/log.py | 16 ---------------- pyramid/tests/test_config.py | 13 ++++++------- pyramid/tests/test_log.py | 16 ---------------- 5 files changed, 33 insertions(+), 55 deletions(-) delete mode 100644 pyramid/log.py delete mode 100644 pyramid/tests/test_log.py diff --git a/CHANGES.txt b/CHANGES.txt index c9c95fd7f..e14d4629b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,7 +8,7 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. -- New configurator directive: +- New configurator directive:^ ``pyramid.config.Configurator.add_request_handler``. This directive adds a request handler factory. @@ -66,6 +66,19 @@ Features same name in the same configuration (unless automatic_conflict_resolution is able to resolve the conflict or this is an autocommitting configurator). +- The Pyramid debug logger now uses the standard logging configuration + (usually set up by Paste as part of startup). This means that output from + e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the + normal logging channels. The logger name of the debug logger will be the + package name of the *caller* of the Configurator's constructor. + +Backwards Incompatibilities +--------------------------- + +- If a string is passed as the ``debug_logger`` parameter to a Configurator, + that string is considered to be the name of a global Python logger rather + than a dotted name to an instance of a logger. + 1.1 (2011-07-22) ================ diff --git a/pyramid/config.py b/pyramid/config.py index d6a87c7ad..cf6fb4182 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1,4 +1,5 @@ import inspect +import logging import os import re import sys @@ -62,7 +63,6 @@ from pyramid.httpexceptions import default_exceptionresponse_view from pyramid.httpexceptions import HTTPForbidden from pyramid.httpexceptions import HTTPNotFound 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 @@ -195,12 +195,12 @@ class Configurator(object): :meth:`pyramid.config.Configurator.add_renderer`). If it is not passed, a default set of renderer factories is used. - If ``debug_logger`` is not passed, a default debug logger that - logs to stderr will be used. If it is passed, it should be an - instance of the :class:`logging.Logger` (PEP 282) standard library - class or a :term:`dotted Python name` to same. The debug logger - is used by :app:`Pyramid` itself to log warnings and - authorization debugging information. + If ``debug_logger`` is not passed, a default debug logger that logs to a + logger will be used (the logger name will be the package name of the + *caller* of this configurator). If it is passed, it should be an + instance of the :class:`logging.Logger` (PEP 282) standard library class + or a Python logger name. The debug logger is used by :app:`Pyramid` + itself to log warnings and authorization debugging information. If ``locale_negotiator`` is passed, it should be a :term:`locale negotiator` implementation or a :term:`dotted Python name` to @@ -726,9 +726,12 @@ class Configurator(object): # cope with WebOb exc objects not decoratored with IExceptionResponse from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) - debug_logger = self.maybe_dotted(debug_logger) + if debug_logger is None: - debug_logger = make_stream_logger('pyramid.debug', sys.stderr) + debug_logger = logging.getLogger(self.package_name) + elif isinstance(debug_logger, basestring): + debug_logger = logging.getLogger(debug_logger) + registry.registerUtility(debug_logger, IDebugLogger) if authentication_policy or authorization_policy: self._set_security_policies(authentication_policy, @@ -953,16 +956,11 @@ class Configurator(object): """ def register(): registry = self.registry - existing_factory = registry.queryUtility(IRequestHandlerFactory, - name=name) registry.registerUtility(handler_factory, IRequestHandlerFactory, name=name) existing_names = registry.queryUtility(IRequestHandlerFactories, default=[]) - if not existing_factory: - # don't replace a name if someone is trying to override - # through a commit - existing_names.append(name) + existing_names.append(name) registry.registerUtility(existing_names, IRequestHandlerFactories) self.action(('requesthandler', name), register) diff --git a/pyramid/log.py b/pyramid/log.py deleted file mode 100644 index 8a29ca919..000000000 --- a/pyramid/log.py +++ /dev/null @@ -1,16 +0,0 @@ -import logging - -def make_stream_logger( - name, stream, levelname='DEBUG', fmt='%(asctime)s %(message)s'): - """ Return an object which implements - ``pyramid.interfaces.IDebugLogger`` (ie. a Python PEP 282 logger - instance) with the name ``name`` using the stream (or open - filehandle) ``stream``, logging at ``levelname`` log level or - above with format ``fmt``. """ - handler = logging.StreamHandler(stream) - formatter = logging.Formatter(fmt) - handler.setFormatter(formatter) - logger = logging.Logger(name) - logger.addHandler(handler) - logger.setLevel(getattr(logging, levelname)) - return logger diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 630fe43ce..57ec1c044 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -153,7 +153,7 @@ class ConfiguratorTests(unittest.TestCase): from pyramid.interfaces import IDebugLogger config = self._makeOne() logger = config.registry.getUtility(IDebugLogger) - self.assertEqual(logger.name, 'pyramid.debug') + self.assertEqual(logger.name, 'pyramid.tests') def test_ctor_noreg_debug_logger_non_None(self): from pyramid.interfaces import IDebugLogger @@ -401,7 +401,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(reg) config.setup_registry() logger = reg.getUtility(IDebugLogger) - self.assertEqual(logger.name, 'pyramid.debug') + self.assertEqual(logger.name, 'pyramid.tests') def test_setup_registry_debug_logger_non_None(self): from pyramid.registry import Registry @@ -413,15 +413,14 @@ class ConfiguratorTests(unittest.TestCase): result = reg.getUtility(IDebugLogger) self.assertEqual(logger, result) - def test_setup_registry_debug_logger_dottedname(self): + def test_setup_registry_debug_logger_name(self): from pyramid.registry import Registry from pyramid.interfaces import IDebugLogger reg = Registry() config = self._makeOne(reg) - config.setup_registry(debug_logger='pyramid.tests') + config.setup_registry(debug_logger='foo') result = reg.getUtility(IDebugLogger) - import pyramid.tests - self.assertEqual(result, pyramid.tests) + self.assertEqual(result.name, 'foo') def test_setup_registry_authentication_policy(self): from pyramid.registry import Registry @@ -628,7 +627,7 @@ class ConfiguratorTests(unittest.TestCase): config.add_request_handler(factory2, 'name2') config.add_request_handler(factory3, 'name1') names = config.registry.queryUtility(IRequestHandlerFactories) - self.assertEqual(names, ['name1', 'name2']) + self.assertEqual(names, ['name1', 'name2', 'name1']) f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1') self.assertEqual(f3, factory3) diff --git a/pyramid/tests/test_log.py b/pyramid/tests/test_log.py deleted file mode 100644 index 63add76ef..000000000 --- a/pyramid/tests/test_log.py +++ /dev/null @@ -1,16 +0,0 @@ -import unittest - -class TestFunctions(unittest.TestCase): - def test_make_stream_logger(self): - from pyramid.log import make_stream_logger - import logging - import sys - logger = make_stream_logger('foo', sys.stderr, levelname='DEBUG', - fmt='%(message)s') - self.assertEqual(logger.name, 'foo') - self.assertEqual(logger.handlers[0].stream, sys.stderr) - self.assertEqual(logger.handlers[0].formatter._fmt, '%(message)s') - self.assertEqual(logger.level, logging.DEBUG) - - - -- cgit v1.2.3 From f91e0c8bebe0d18c3e65850515b8b1cb7fdbd335 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 02:09:43 -0400 Subject: stray hat --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index e14d4629b..2bc462008 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,7 +8,7 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. -- New configurator directive:^ +- New configurator directive: ``pyramid.config.Configurator.add_request_handler``. This directive adds a request handler factory. -- cgit v1.2.3 From 0c2141c78c85898c87495f040617699e79a49c04 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 23:14:36 -0700 Subject: Removed repetitive and badly formed sentence --- docs/narr/project.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index cdf57729d..34a641c4a 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -99,9 +99,8 @@ We'll choose the ``pyramid_starter`` scaffold for this purpose. $ bin/paster create -t pyramid_starter -The above command uses the ``paster`` command to create a project using the -``pyramid_starter`` scaffold. The ``paster create`` command creates project -from a scaffold. To use a different scaffold, such as +The above command uses the ``paster create`` command to create a project with the +``pyramid_starter`` scaffold. To use a different scaffold, such as ``pyramid_routesalchemy``, you'd just change the last argument. For example: .. code-block:: text -- cgit v1.2.3 From 82c2fc57e36c65493ba0fa39368733c189d954c6 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sat, 23 Jul 2011 23:55:31 -0700 Subject: replace extra 'the' with 'be' --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 34a641c4a..bf1a610b4 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -176,7 +176,7 @@ command ``python setup.py develop`` The file named ``setup.py`` will be in the root of the paster-generated project directory. The ``python`` you're invoking should be the one that lives in the ``bin`` directory of your virtual Python environment. Your -terminal's current working directory *must* the the newly created project +terminal's current working directory *must* be the newly created project directory. For example: .. code-block:: text -- cgit v1.2.3 From 5c05ef41bfe538b1ce802bb163a40dbd8880e757 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sun, 24 Jul 2011 00:10:35 -0700 Subject: extra 's' removed --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index bf1a610b4..32846f848 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -467,7 +467,7 @@ the default. The ``use`` setting is the only setting *required* in the ``[app:MyProject]`` section unless you've changed the callable referred to by the ``egg:MyProject`` entry point to accept more arguments: other settings you -add to this section are passed as keywords arguments to the callable +add to this section are passed as keyword arguments to the callable represented by this entry point (``main`` in our ``__init__.py`` module). You can provide startup-time configuration parameters to your application by adding more settings to this section. -- cgit v1.2.3 From 17fd1443f4f8107023c90286e46362dd035d480c Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sun, 24 Jul 2011 00:14:23 -0700 Subject: typo --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 32846f848..ca7d97ca9 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -481,7 +481,7 @@ template changes will not require an application restart to be detected. See The ``debug_templates`` setting in the ``[app:MyProject]`` section is a :app:`Pyramid` -specific setting which is passed into the framework. If it exists, and its value is ``true``, :term:`Chameleon` template exceptions will -contained more detailed and helpful information about the error than when +contain more detailed and helpful information about the error than when this value is ``false``. See :ref:`debug_templates_section` for more information. -- cgit v1.2.3 From 04e4f2f03d487d5b8c55b52ea1d882b7464574c0 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sun, 24 Jul 2011 00:18:43 -0700 Subject: Removed extra 'if' --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index ca7d97ca9..47cdbdc83 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -458,7 +458,7 @@ the default. point can thus be referred to as a "Paste application factory in the ``MyProject`` project which has the entry point named ``main`` where the entry point refers to a ``main`` function in the ``mypackage`` module". - If indeed if you open up the ``__init__.py`` module generated within the + Indeed, if you open up the ``__init__.py`` module generated within the ``myproject`` package, you'll see a ``main`` function. This is the function called by :term:`PasteDeploy` when the ``paster serve`` command is invoked against our application. It accepts a global configuration -- cgit v1.2.3 From b210666d36ef855df45478526d3ed54196955a00 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sun, 24 Jul 2011 00:40:34 -0700 Subject: missing word --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 47cdbdc83..5f4878470 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -846,7 +846,7 @@ template. It includes CSS and images. ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The single :term:`Chameleon` template exists in the project. Its contents +The single :term:`Chameleon` template that exists in the project. Its contents are too long to show here, but it displays a default page when rendered. It is referenced by the call to ``add_view`` as the ``renderer`` attribute in the ``__init__`` file. See :ref:`views_which_use_a_renderer` for more -- cgit v1.2.3 From 282a62fc3e943a416e22575d8dc9381f2077f273 Mon Sep 17 00:00:00 2001 From: Julian Taylor Date: Sun, 24 Jul 2011 18:54:22 +0200 Subject: add compatibility with ipython 0.11 try to use 0.11 and 0.10 api to open embedded ipython shell --- pyramid/paster.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 3aa6a5f1d..8eb8f2413 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -150,11 +150,6 @@ class PShellCommand(PCommand): def command(self, IPShell=_marker): # IPShell passed to command method is for testing purposes - if IPShell is _marker: # pragma: no cover - try: - from IPython.Shell import IPShell - except ImportError: - IPShell = None config_uri = self.args[0] config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) @@ -197,6 +192,18 @@ class PShellCommand(PCommand): help += '\n' + if IPShell is _marker: + try: #pragma no cover + try: #pragma no cover + from IPython.frontend.terminal.embed import InteractiveShellEmbed + IPShell = InteractiveShellEmbed(banner2=help, user_ns=env) + except ImportError: #pragma no cover + from IPython.Shell import IPShellEmbed + IPShell = IPShellEmbed(argv=[], user_ns=env) + IPShell.IP.BANNER = IPShell.IP.BANNER + '\n\n' + help + except ImportError: #pragma no cover + IPShell = None + if (IPShell is None) or self.options.disable_ipython: cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) @@ -207,9 +214,7 @@ class PShellCommand(PCommand): closer() else: try: - shell = IPShell(argv=[], user_ns=env) - shell.IP.BANNER = shell.IP.BANNER + help - shell.mainloop() + IPShell() finally: closer() -- cgit v1.2.3 From 4396c62c5ebb07ffdc418955c251ed4d62d45817 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Sun, 24 Jul 2011 11:00:09 -0700 Subject: Rewrote bad sentence --- docs/narr/startup.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 8661c8f6a..68df9d417 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -112,11 +112,11 @@ Here's a high-level time-ordered overview of what happens when you press (which is internal to Paste) such as ``reload_templates``, ``debug_authorization``, etc. -#. The ``main`` function then calls various methods on the an instance of the - class :class:`~pyramid.config.Configurator` method. The intent of - calling these methods is to populate an :term:`application registry`, - which represents the :app:`Pyramid` configuration related to the - application. +#. The ``main`` function then calls various methods on the instance of the + class :class:`~pyramid.config.Configurator` created in the previous step. + The intent of calling these methods is to populate an + :term:`application registry`, which represents the :app:`Pyramid` + configuration related to the application. #. The :meth:`~pyramid.config.Configurator.make_wsgi_app` method is called. The result is a :term:`router` instance. The router is associated with -- cgit v1.2.3 From 4588e5b3e303813d70a466bb64d31655ef295bda Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 24 Jul 2011 21:23:50 -0400 Subject: allow handler to be a dotted name --- pyramid/config.py | 8 +++++--- pyramid/tests/test_config.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index cf6fb4182..46249d14e 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -901,9 +901,10 @@ class Configurator(object): the server. A request handler factory (passed as ``handler_factory``) must be a - callable which accepts two arguments: ``handler`` and ``registry``. - ``handler`` will be the request handler being wrapped. ``registry`` - will be the Pyramid :term:`application registry` represented by this + callable (or a :term:`Python dotted name` to a callable) which + accepts two arguments: ``handler`` and ``registry``. ``handler`` + will be the request handler being wrapped. ``registry`` will be the + Pyramid :term:`application registry` represented by this Configurator. A request handler factory must return a request handler when it is called. @@ -954,6 +955,7 @@ class Configurator(object): .. note:: This feature is new as of Pyramid 1.1.1. """ + handler_factory = self.maybe_dotted(handler_factory) def register(): registry = self.registry registry.registerUtility(handler_factory, IRequestHandlerFactory, diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 57ec1c044..22dd878c5 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -616,6 +616,18 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(f1, factory1) self.assertEqual(f2, factory2) + def test_add_request_handlers_dottednames(self): + import pyramid.tests + from pyramid.interfaces import IRequestHandlerFactories + from pyramid.interfaces import IRequestHandlerFactory + config = self._makeOne() + config.add_request_handler('pyramid.tests', 'name1') + config.commit() + names = config.registry.queryUtility(IRequestHandlerFactories) + self.assertEqual(names, ['name1']) + f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + self.assertEqual(f1, pyramid.tests) + def test_add_request_handlers_names_overlap(self): from pyramid.interfaces import IRequestHandlerFactories from pyramid.interfaces import IRequestHandlerFactory -- cgit v1.2.3 From aca04857400e4e5305d7f533f26dc705f5a85775 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 25 Jul 2011 01:22:58 -0400 Subject: garden --- TODO.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.txt b/TODO.txt index 14c52fe3d..113dcddee 100644 --- a/TODO.txt +++ b/TODO.txt @@ -9,6 +9,10 @@ Should-Have - Add narrative docs for wsgiapp and wsgiapp2. +- Provide a way to set the authentication policy and the authorization policy + during a config.include (they are related, so just exposing the currently + underscored-private _set_auth* methods won't cut it). + Nice-to-Have ------------ -- cgit v1.2.3 From bc116a15f94b92aa0216d86f2e0a32fe53d4c450 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 25 Jul 2011 01:24:56 -0400 Subject: garden --- TODO.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TODO.txt b/TODO.txt index 113dcddee..49f137fa2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -13,11 +13,14 @@ Should-Have during a config.include (they are related, so just exposing the currently underscored-private _set_auth* methods won't cut it). +- Rename all config file values with a ``pyramid.`` prefix. + +- Try to figure out a way to keep "settings" as the original dictionary + passed to the Configurator instead of copying it. + Nice-to-Have ------------ -- Rename all config file values with a ``pyramid.`` prefix. - - Maybe add ``add_renderer_globals`` method to Configurator. - Speed up startup time (defer _bootstrap and registerCommonDirectives() -- cgit v1.2.3 From 73e31c15fd405a8848592800a66ffad547d49e19 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 25 Jul 2011 01:53:44 -0400 Subject: garden; whitespace, comment cleanups --- TODO.txt | 18 +++++++++++++++++- pyramid/router.py | 10 +++------- pyramid/urldispatch.py | 1 - 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/TODO.txt b/TODO.txt index 49f137fa2..8cfac8ceb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,7 +5,11 @@ Should-Have ----------- - Make "localizer" a property of request (instead of requiring - "get_localizer(request)"? + "get_localizer(request)" + +- Make ``current_route_url`` a method of request. + +- Create a ``current_route_path`` function and make it a method of request. - Add narrative docs for wsgiapp and wsgiapp2. @@ -18,6 +22,18 @@ Should-Have - Try to figure out a way to keep "settings" as the original dictionary passed to the Configurator instead of copying it. +- Merge aodag's config.include(route_prefix=...) fork. + +- Michael's route group work. + +- Kill off ``bfg.routes`` envvars in router. + +- Provide a ``has_view`` function. + +- Alias the stupid long default session factory name. + +- Fix indirect circular import between router and config. + Nice-to-Have ------------ diff --git a/pyramid/router.py b/pyramid/router.py index e8c19fca0..efbf0b326 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -65,6 +65,7 @@ class Router(object): has_listeners = registry.has_listeners notify = registry.notify logger = self.logger + try: # matches except Exception (exception view execution) has_listeners and notify(NewRequest(request)) # find the root object @@ -205,22 +206,17 @@ class Router(object): threadlocals = {'registry':registry, 'request':request} manager.push(threadlocals) - try: # matches finally: manager.pop() - - try: # matches finally: ... call request finished callbacks ... - - # create the request + try: + try: request = self.request_factory(environ) threadlocals['request'] = request request.registry = registry response = self.handle_request(request) - finally: if request is not None and request.finished_callbacks: request._process_finished_callbacks() return response(request.environ, start_response) - finally: manager.pop() diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index 2cbbb8709..3530126eb 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -10,7 +10,6 @@ from pyramid.exceptions import URLDecodeError from pyramid.traversal import traversal_path from pyramid.traversal import quote_path_segment - _marker = object() class Route(object): -- cgit v1.2.3 From e5dfa6cae1cf90b8ffc62f9f769b917cb0daab04 Mon Sep 17 00:00:00 2001 From: Chris Davies Date: Mon, 25 Jul 2011 02:37:28 -0400 Subject: modified scaffold templates to be xhtml compliant --- pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl | 4 ++-- pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl | 4 ++-- .../scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl | 4 ++-- pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl | 4 ++-- pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl index 263999c5e..83ddd768e 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/model.pt_tmpl @@ -7,8 +7,8 @@ - - + + diff --git a/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl index 3bf3da34e..fc41ce20a 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/root.pt_tmpl @@ -7,8 +7,8 @@ - - + + diff --git a/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl index b24c109d7..3cd9c66a4 100644 --- a/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/routesalchemy/+package+/templates/mytemplate.pt_tmpl @@ -7,8 +7,8 @@ - - + + diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl index b24c109d7..3cd9c66a4 100644 --- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl @@ -7,8 +7,8 @@ - - + + diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl index b24c109d7..3cd9c66a4 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -7,8 +7,8 @@ - - + + -- cgit v1.2.3 From dd91eb061edc328ae8a96df0b719624804d5ef31 Mon Sep 17 00:00:00 2001 From: Julian Taylor Date: Mon, 25 Jul 2011 10:25:35 +0200 Subject: fix banner display for ipython < 0.11 --- pyramid/paster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 8eb8f2413..277f093d7 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -200,7 +200,7 @@ class PShellCommand(PCommand): except ImportError: #pragma no cover from IPython.Shell import IPShellEmbed IPShell = IPShellEmbed(argv=[], user_ns=env) - IPShell.IP.BANNER = IPShell.IP.BANNER + '\n\n' + help + IPShell.set_banner(IPShell.IP.BANNER + '\n' + help) except ImportError: #pragma no cover IPShell = None -- cgit v1.2.3 From 9e5f538a1d73f99b047502670228b862d42c2c59 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 27 Jul 2011 19:25:48 -0400 Subject: use dev version number, for trunk-dependent add-ons --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 376f503f6..109be6951 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ if sys.version_info[:2] < (2, 6): install_requires.append('simplejson') setup(name='pyramid', - version='1.1', + version='1.1.1dev', description=('The Pyramid web application development framework, a ' 'Pylons project'), long_description=README + '\n\n' + CHANGES, -- cgit v1.2.3 From d8d14a474ac84a58d3545768fecd3fe1c78219c0 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 28 Jul 2011 16:48:47 -0400 Subject: garden --- TODO.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/TODO.txt b/TODO.txt index 8cfac8ceb..7e2abf2bf 100644 --- a/TODO.txt +++ b/TODO.txt @@ -34,11 +34,15 @@ Should-Have - Fix indirect circular import between router and config. +- Debug option to print view matching decision (e.g. debug_viewlookup or so). + +- Eliminate non-deployment-non-scaffold-related Paste dependencies: + ``paste.urlparser.StaticURLParser``, ``paste.auth.auth_tkt`` (cutnpaste or + reimplement both). + Nice-to-Have ------------ -- Maybe add ``add_renderer_globals`` method to Configurator. - - Speed up startup time (defer _bootstrap and registerCommonDirectives() until needed by ZCML, as well as unfound speedups). @@ -76,13 +80,16 @@ Nice-to-Have - Create a function which performs a recursive request. -- Debug option to print view matching decision. - - Update App engine chapter with less creaky directions. +Probably Bad Ideas +------------------ + - Add functionality that mocks the behavior of ``repoze.browserid``. - Consider implementing the API outlined in http://plope.com/pyramid_auth_design_api_postmortem, phasing out the current auth-n-auth abstractions in a backwards compatible way. +- Maybe add ``add_renderer_globals`` method to Configurator. + -- cgit v1.2.3 From 1f901ab75c55bafc9c233c3c9588cc1bd92d9d66 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 28 Jul 2011 17:06:29 -0400 Subject: add some edits to the docs for response_adapter decorator; fix renderings --- CHANGES.txt | 4 ++++ docs/glossary.rst | 5 +++++ docs/narr/hooks.rst | 37 +++++++++++++++++++++++-------------- pyramid/config.py | 4 ++-- pyramid/response.py | 9 ++++++--- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2bc462008..5ca9e9379 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,10 @@ Next release Features -------- +- Added a decorator-based way to configure a response adapter: + ``pyramid.response.response_adapter``. This decorator has the same use as + ``pyramid.config.Configurator.add_response_adapter`` but it's declarative. + - The ``pyramid.events.BeforeRender`` event now has an attribute named ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. diff --git a/docs/glossary.rst b/docs/glossary.rst index cc1d6201d..c6705fdc5 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -23,6 +23,11 @@ Glossary a subclass such as :class:`pyramid.httpexceptions.HTTPFound`. See :ref:`webob_chapter` for information about response objects. + response adapter + A callable which accepts an arbitrary object and "converts" it to a + :class:`pyramid.response.Response` object. See :ref:`using_iresponse` + for more information. + Repoze "Repoze" is essentially a "brand" of software developed by `Agendaless Consulting `_ and a set of contributors. The diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index df081d35c..fc3f01271 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -575,20 +575,6 @@ Response: config.add_response_adapter(string_response_adapter, str) -The above example using the :class:`~pyramid.response.response_adapter` -decorator: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.response import response_adapter - - @response_adapter(str) - def string_response_adapter(s): - response = Response(s) - return response - Likewise, if you want to be able to return a simplified kind of response object from view callables, you can use the IResponse hook to register an adapter to the more complex IResponse interface: @@ -639,6 +625,29 @@ startup time, as by their nature, instances of this class (and instances of subclasses of the class) will natively provide IResponse. The adapter registered for ``webob.Response`` simply returns the response object. +Instead of using :meth:`pyramid.config.Configurator.add_response_adapter`, +you can use the :class:`pyramid.response.response_adapter` decorator: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(str) + def string_response_adapter(s): + response = Response(s) + return response + +The above example, when scanned, has the same effect as: + +.. code-block:: python + + config.add_response_adapter(string_response_adapter, str) + +The :class:`~pyramid.response.response_adapter` decorator will have no effect +until activated by a :term:`scan`. + .. index:: single: view mapper diff --git a/pyramid/config.py b/pyramid/config.py index 46249d14e..b0041f370 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -661,7 +661,7 @@ class Configurator(object): """ Return a new Configurator instance with the same registry as this configurator using the package supplied as the ``package`` argument to the new configurator. ``package`` may - be an actual Python package object or a Python dotted name + be an actual Python package object or a :term:`dotted Python name` representing a package.""" context = self._ctx if context is None: @@ -901,7 +901,7 @@ class Configurator(object): the server. A request handler factory (passed as ``handler_factory``) must be a - callable (or a :term:`Python dotted name` to a callable) which + callable (or a :term:`dotted Python name` to a callable) which accepts two arguments: ``handler`` and ``registry``. ``handler`` will be the request handler being wrapped. ``registry`` will be the Pyramid :term:`application registry` represented by this diff --git a/pyramid/response.py b/pyramid/response.py index 60666bd03..db53de0c3 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -9,11 +9,11 @@ class Response(_Response): class response_adapter(object): - """ Decorator activated via a :term:`scan` which treats the - function being decorated as a response adapter for the set of types or + """ Decorator activated via a :term:`scan` which treats the function + being decorated as a :term:`response adapter` for the set of types or interfaces passed as ``*types_or_ifaces`` to the decorator constructor. - For example: + For example, if you scan the following response adapter: .. code-block:: python @@ -24,6 +24,9 @@ class response_adapter(object): def myadapter(i): return Response(status=i) + You can then return an integer from your view callables, and it will be + converted into a response with the integer as the status code. + More than one type or interface can be passed as a constructor argument. The decorated response adapter will be called for each type or interface. -- cgit v1.2.3 From 50d3aaf0fbbed262a49d7c704acf1159002c1558 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 28 Jul 2011 17:15:35 -0400 Subject: garden --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index 7e2abf2bf..4e24e1097 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,9 @@ Pyramid TODOs Should-Have ----------- +- Merge https://github.com/Pylons/pyramid/pull/242 (IPython update; requires + test fixes and additional test coverage). + - Make "localizer" a property of request (instead of requiring "get_localizer(request)" -- cgit v1.2.3 From 6624092da587160e5d449af93eca3c0e779dfc55 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 28 Jul 2011 17:16:56 -0400 Subject: garden --- TODO.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 4e24e1097..cf5d3c76c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -20,7 +20,8 @@ Should-Have during a config.include (they are related, so just exposing the currently underscored-private _set_auth* methods won't cut it). -- Rename all config file values with a ``pyramid.`` prefix. +- Rename all config file values with a ``pyramid.`` prefix. Preserve bw + compat, though, for older config files. - Try to figure out a way to keep "settings" as the original dictionary passed to the Configurator instead of copying it. -- cgit v1.2.3 From 6aafc53c0ee74c2a568fb4d36f5eaab968126088 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Thu, 28 Jul 2011 17:18:26 -0400 Subject: garden --- TODO.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/TODO.txt b/TODO.txt index cf5d3c76c..761abdc58 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,8 +14,6 @@ Should-Have - Create a ``current_route_path`` function and make it a method of request. -- Add narrative docs for wsgiapp and wsgiapp2. - - Provide a way to set the authentication policy and the authorization policy during a config.include (they are related, so just exposing the currently underscored-private _set_auth* methods won't cut it). @@ -28,18 +26,14 @@ Should-Have - Merge aodag's config.include(route_prefix=...) fork. -- Michael's route group work. +- Merge Michael's route group work. - Kill off ``bfg.routes`` envvars in router. -- Provide a ``has_view`` function. - - Alias the stupid long default session factory name. - Fix indirect circular import between router and config. -- Debug option to print view matching decision (e.g. debug_viewlookup or so). - - Eliminate non-deployment-non-scaffold-related Paste dependencies: ``paste.urlparser.StaticURLParser``, ``paste.auth.auth_tkt`` (cutnpaste or reimplement both). @@ -47,6 +41,12 @@ Should-Have Nice-to-Have ------------ +- Add narrative docs for wsgiapp and wsgiapp2. + +- Provide a ``has_view`` function. + +- Debug option to print view matching decision (e.g. debug_viewlookup or so). + - Speed up startup time (defer _bootstrap and registerCommonDirectives() until needed by ZCML, as well as unfound speedups). -- cgit v1.2.3 From 391c349c8d9d0c89633cc2e92a2ddb1c045ab2f1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 29 Jul 2011 08:32:49 -0600 Subject: Added support in pyramid for "pyramid.*" settings prefixes. These settings will override the previous settings keys but not the keys from the environ. --- pyramid/settings.py | 24 +++++++- pyramid/tests/test_settings.py | 121 ++++++++++++++++++++++++++++++++++------- 2 files changed, 121 insertions(+), 24 deletions(-) diff --git a/pyramid/settings.py b/pyramid/settings.py index 7ba53ea4c..0f19f5389 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -20,36 +20,54 @@ class Settings(dict): dict.__init__(self, d, **kw) eget = _environ_.get config_debug_all = self.get('debug_all', '') + config_debug_all = self.get('pyramid.debug_all', config_debug_all) eff_debug_all = asbool(eget('PYRAMID_DEBUG_ALL', config_debug_all)) config_reload_all = self.get('reload_all', '') - eff_reload_all = asbool(eget('PYRAMID_RELOAD_ALL',config_reload_all)) + config_reload_all = self.get('pyramid.reload_all', config_reload_all) + eff_reload_all = asbool(eget('PYRAMID_RELOAD_ALL', config_reload_all)) config_debug_auth = self.get('debug_authorization', '') + config_debug_auth = self.get('pyramid.debug_authorization', + config_debug_auth) eff_debug_auth = asbool(eget('PYRAMID_DEBUG_AUTHORIZATION', config_debug_auth)) config_debug_notfound = self.get('debug_notfound', '') + config_debug_notfound = self.get('pyramid.debug_notfound', + config_debug_notfound) eff_debug_notfound = asbool(eget('PYRAMID_DEBUG_NOTFOUND', config_debug_notfound)) config_debug_routematch = self.get('debug_routematch', '') + config_debug_routematch = self.get('pyramid.debug_routematch', + config_debug_routematch) eff_debug_routematch = asbool(eget('PYRAMID_DEBUG_ROUTEMATCH', config_debug_routematch)) config_debug_templates = self.get('debug_templates', '') + config_debug_templates = self.get('pyramid.debug_templates', + config_debug_templates) eff_debug_templates = asbool(eget('PYRAMID_DEBUG_TEMPLATES', config_debug_templates)) config_reload_templates = self.get('reload_templates', '') + config_reload_templates = self.get('pyramid.reload_templates', + config_reload_templates) eff_reload_templates = asbool(eget('PYRAMID_RELOAD_TEMPLATES', config_reload_templates)) config_reload_assets = self.get('reload_assets', '') - config_reload_resources = self.get('reload_resources', '') + config_reload_assets = self.get('pyramid.reload_assets', + config_reload_assets) reload_assets = asbool(eget('PYRAMID_RELOAD_ASSETS', config_reload_assets)) + config_reload_resources = self.get('reload_resources', '') + config_reload_resources = self.get('pyramid.reload_resources', + config_reload_resources) reload_resources = asbool(eget('PYRAMID_RELOAD_RESOURCES', config_reload_resources)) # reload_resources is an older alias for reload_assets eff_reload_assets = reload_assets or reload_resources locale_name = self.get('default_locale_name', 'en') + locale_name = self.get('pyramid.default_locale_name', locale_name) eff_locale_name = eget('PYRAMID_DEFAULT_LOCALE_NAME', locale_name) - config_prevent_http_cache = self.get('prevent_http_cache', '') + config_prevent_http_cache = self.get('pyramid.prevent_http_cache', + config_prevent_http_cache) eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE', config_prevent_http_cache)) diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py index 75d653133..d8e039e5a 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -37,10 +37,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'1'}) self.assertEqual(result['prevent_http_cache'], True) + result = self._makeOne({'pyramid.prevent_http_cache':'t'}) + self.assertEqual(result['prevent_http_cache'], True) result = self._makeOne({}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) self.assertEqual(result['prevent_http_cache'], True) - result = self._makeOne({'prevent_http_cache':'false'}, - {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) + result = self._makeOne({'prevent_http_cache':'false', + 'pyramid.prevent_http_cache':'1'}) + self.assertEqual(result['prevent_http_cache'], True) + result = self._makeOne({'prevent_http_cache':'false', + 'pyramid.prevent_http_cache':'f'}, + {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) self.assertEqual(result['prevent_http_cache'], True) def test_reload_templates(self): @@ -52,10 +58,15 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['reload_templates'], True) result = self._makeOne({'reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) + result = self._makeOne({'pyramid.reload_templates':'1'}) + self.assertEqual(result['reload_templates'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) + result = self._makeOne({'reload_templates':'false', + 'pyramid.reload_templates':'1'}) + self.assertEqual(result['reload_templates'], True) result = self._makeOne({'reload_templates':'false'}, - {'PYRAMID_RELOAD_TEMPLATES':'1'}) + {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) def test_reload_resources(self): @@ -72,11 +83,19 @@ class TestSettings(unittest.TestCase): result = self._makeOne({'reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + result = self._makeOne({'pyramid.reload_resources':'1'}) + self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) - result = self._makeOne({'reload_resources':'false'}, - {'PYRAMID_RELOAD_RESOURCES':'1'}) + result = self._makeOne({'reload_resources':'false', + 'pyramid.reload_resources':'1'}) + self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['reload_assets'], True) + result = self._makeOne({'reload_resources':'false', + 'pyramid.reload_resources':'false'}, + {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) @@ -94,11 +113,19 @@ class TestSettings(unittest.TestCase): result = self._makeOne({'reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + result = self._makeOne({'pyramid.reload_assets':'1'}) + self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['reload_resources'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) - result = self._makeOne({'reload_assets':'false'}, - {'PYRAMID_RELOAD_ASSETS':'1'}) + result = self._makeOne({'reload_assets':'false', + 'pyramid.reload_assets':'1'}) + self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['reload_resources'], True) + result = self._makeOne({'reload_assets':'false', + 'pyramid.reload_assets':'false'}, + {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) @@ -120,12 +147,22 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + result = self._makeOne({'pyramid.reload_all':'1'}) + self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) - result = self._makeOne({'reload_all':'false'}, - {'PYRAMID_RELOAD_ALL':'1'}) + result = self._makeOne({'reload_all':'false', + 'pyramid.reload_all':'1'}) + self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['reload_assets'], True) + result = self._makeOne({'reload_all':'false', + 'pyramid.reload_all':'false'}, + {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) @@ -139,10 +176,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_authorization'], True) result = self._makeOne({'debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) + result = self._makeOne({'pyramid.debug_authorization':'1'}) + self.assertEqual(result['debug_authorization'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) - result = self._makeOne({'debug_authorization':'false'}, - {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) + result = self._makeOne({'debug_authorization':'false', + 'pyramid.debug_authorization':'1'}) + self.assertEqual(result['debug_authorization'], True) + result = self._makeOne({'debug_authorization':'false', + 'pyramid.debug_authorization':'false'}, + {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) def test_debug_notfound(self): @@ -154,10 +197,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_notfound'], True) result = self._makeOne({'debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) + result = self._makeOne({'pyramid.debug_notfound':'1'}) + self.assertEqual(result['debug_notfound'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) - result = self._makeOne({'debug_notfound':'false'}, - {'PYRAMID_DEBUG_NOTFOUND':'1'}) + result = self._makeOne({'debug_notfound':'false', + 'pyramid.debug_notfound':'1'}) + self.assertEqual(result['debug_notfound'], True) + result = self._makeOne({'debug_notfound':'false', + 'pyramid.debug_notfound':'false'}, + {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) def test_debug_routematch(self): @@ -169,10 +218,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_routematch'], True) result = self._makeOne({'debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) + result = self._makeOne({'pyramid.debug_routematch':'1'}) + self.assertEqual(result['debug_routematch'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) - result = self._makeOne({'debug_routematch':'false'}, - {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) + result = self._makeOne({'debug_routematch':'false', + 'pyramid.debug_routematch':'1'}) + self.assertEqual(result['debug_routematch'], True) + result = self._makeOne({'debug_routematch':'false', + 'pyramid.debug_routematch':'false'}, + {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) def test_debug_templates(self): @@ -184,10 +239,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_templates'], True) result = self._makeOne({'debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) + result = self._makeOne({'pyramid.debug_templates':'1'}) + self.assertEqual(result['debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) - result = self._makeOne({'debug_templates':'false'}, - {'PYRAMID_DEBUG_TEMPLATES':'1'}) + result = self._makeOne({'debug_templates':'false', + 'pyramid.debug_templates':'1'}) + self.assertEqual(result['debug_templates'], True) + result = self._makeOne({'debug_templates':'false', + 'pyramid.debug_templates':'false'}, + {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) def test_debug_all(self): @@ -211,13 +272,25 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + result = self._makeOne({'pyramid.debug_all':'1'}) + self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ALL':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) - result = self._makeOne({'debug_all':'false'}, - {'PYRAMID_DEBUG_ALL':'1'}) + result = self._makeOne({'debug_all':'false', + 'pyramid.debug_all':'1'}) + self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['debug_templates'], True) + result = self._makeOne({'debug_all':'false', + 'pyramid.debug_all':'false'}, + {'PYRAMID_DEBUG_ALL':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) @@ -228,10 +301,16 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['default_locale_name'], 'en') result = self._makeOne({'default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + result = self._makeOne({'pyramid.default_locale_name':'abc'}) + self.assertEqual(result['default_locale_name'], 'abc') result = self._makeOne({}, {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') - result = self._makeOne({'default_locale_name':'def'}, - {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) + result = self._makeOne({'default_locale_name':'def', + 'pyramid.default_locale_name':'abc'}) + self.assertEqual(result['default_locale_name'], 'abc') + result = self._makeOne({'default_locale_name':'def', + 'pyramid.default_locale_name':'ghi'}, + {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') def test_originals_kept(self): -- cgit v1.2.3 From 1a45b444d28041c06a188800af1bf918bf26a3d7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 30 Jul 2011 00:49:34 -0600 Subject: Added support for a 'pyramid.include' setting to add arbitrary modules to pyramid from within an INI file. --- pyramid/config.py | 7 +++++++ pyramid/tests/test_config.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pyramid/config.py b/pyramid/config.py index b0041f370..159422c22 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -717,6 +717,11 @@ class Configurator(object): policies, renderers, a debug logger, a locale negotiator, and various other settings using the configurator's current registry, as per the descriptions in the Configurator constructor.""" + if settings: + includes = settings.pop('pyramid.include', '') + includes = [x.strip() for x in includes.splitlines()] + else: + includes = [] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -772,6 +777,8 @@ class Configurator(object): if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) self.commit() + for inc in includes: + self.include(inc) def hook_zca(self): """ Call :func:`zope.component.getSiteManager.sethook` with diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 22dd878c5..250c53b9a 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -569,6 +569,18 @@ class ConfiguratorTests(unittest.TestCase): config.setup_registry(default_permission='view') self.assertEqual(reg.getUtility(IDefaultPermission), 'view') + def test_setup_registry_includes(self): + from pyramid.registry import Registry + reg = Registry() + config = self._makeOne(reg) + settings = { + 'pyramid.include': """pyramid.tests.test_config.dummy_include +pyramid.tests.test_config.dummy_include2""", + } + config.setup_registry(settings=settings) + self.assert_(reg.included) + self.assert_(reg.also_included) + def test_get_settings_nosettings(self): from pyramid.registry import Registry reg = Registry() @@ -5604,6 +5616,11 @@ def dummyfactory(request): """ """ def dummy_include(config): + config.registry.included = True + config.action('discrim', None, config.package) + +def dummy_include2(config): + config.registry.also_included = True config.action('discrim', None, config.package) includeme = dummy_include -- cgit v1.2.3 From 7ca77391bcebc92ff432c99afc6da1664626dbf6 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 30 Jul 2011 00:58:18 -0600 Subject: Updated the scaffold settings to use the 'pyramid.' prefix. --- pyramid/scaffolds/alchemy/development.ini_tmpl | 14 ++++++++------ pyramid/scaffolds/alchemy/production.ini_tmpl | 14 ++++++++------ pyramid/scaffolds/routesalchemy/development.ini_tmpl | 14 ++++++++------ pyramid/scaffolds/routesalchemy/production.ini_tmpl | 14 ++++++++------ pyramid/scaffolds/starter/development.ini_tmpl | 13 +++++++------ pyramid/scaffolds/starter/production.ini_tmpl | 13 +++++++------ pyramid/scaffolds/zodb/development.ini_tmpl | 14 ++++++++------ pyramid/scaffolds/zodb/production.ini_tmpl | 14 ++++++++------ 8 files changed, 62 insertions(+), 48 deletions(-) diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl index accec1718..06c29069d 100644 --- a/pyramid/scaffolds/alchemy/development.ini_tmpl +++ b/pyramid/scaffolds/alchemy/development.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en + sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl index 0deb1dd5f..6025d9a14 100644 --- a/pyramid/scaffolds/alchemy/production.ini_tmpl +++ b/pyramid/scaffolds/alchemy/production.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en + sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [filter:weberror] diff --git a/pyramid/scaffolds/routesalchemy/development.ini_tmpl b/pyramid/scaffolds/routesalchemy/development.ini_tmpl index accec1718..06c29069d 100644 --- a/pyramid/scaffolds/routesalchemy/development.ini_tmpl +++ b/pyramid/scaffolds/routesalchemy/development.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en + sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] diff --git a/pyramid/scaffolds/routesalchemy/production.ini_tmpl b/pyramid/scaffolds/routesalchemy/production.ini_tmpl index 0deb1dd5f..6025d9a14 100644 --- a/pyramid/scaffolds/routesalchemy/production.ini_tmpl +++ b/pyramid/scaffolds/routesalchemy/production.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en + sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [filter:weberror] diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl index 60e52da57..1239dca3f 100644 --- a/pyramid/scaffolds/starter/development.ini_tmpl +++ b/pyramid/scaffolds/starter/development.ini_tmpl @@ -1,11 +1,12 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en [pipeline:main] pipeline = diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl index dea0ef2c6..4ddb3f954 100644 --- a/pyramid/scaffolds/starter/production.ini_tmpl +++ b/pyramid/scaffolds/starter/production.ini_tmpl @@ -1,11 +1,12 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en [filter:weberror] use = egg:WebError#error_catcher diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl index ae9584690..730300a82 100644 --- a/pyramid/scaffolds/zodb/development.ini_tmpl +++ b/pyramid/scaffolds/zodb/development.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl index ff52a0585..9fe8f4741 100644 --- a/pyramid/scaffolds/zodb/production.ini_tmpl +++ b/pyramid/scaffolds/zodb/production.ini_tmpl @@ -1,11 +1,13 @@ [app:{{project}}] use = egg:{{project}} -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] -- cgit v1.2.3 From e1b25974a4dcc9eb9ceab70ec2276043de775f82 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 30 Jul 2011 01:28:22 -0600 Subject: Added the pyramid.* settings to the settings dict. --- pyramid/settings.py | 10 +++ pyramid/tests/test_settings.py | 149 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/pyramid/settings.py b/pyramid/settings.py index 0f19f5389..7540cb6d6 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -81,6 +81,16 @@ class Settings(dict): 'reload_assets':eff_reload_all or eff_reload_assets, 'default_locale_name':eff_locale_name, 'prevent_http_cache':eff_prevent_http_cache, + + 'pyramid.debug_authorization': eff_debug_all or eff_debug_auth, + 'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound, + 'pyramid.debug_routematch': eff_debug_all or eff_debug_routematch, + 'pyramid.debug_templates': eff_debug_all or eff_debug_templates, + 'pyramid.reload_templates': eff_reload_all or eff_reload_templates, + 'pyramid.reload_resources':eff_reload_all or eff_reload_assets, + 'pyramid.reload_assets':eff_reload_all or eff_reload_assets, + 'pyramid.default_locale_name':eff_locale_name, + 'pyramid.prevent_http_cache':eff_prevent_http_cache, } self.update(update) diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py index d8e039e5a..5037959aa 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -28,228 +28,337 @@ class TestSettings(unittest.TestCase): self.assertEqual(settings['reload_templates'], False) self.assertEqual(settings['reload_resources'], False) + self.assertEqual(settings['pyramid.debug_authorization'], False) + self.assertEqual(settings['pyramid.debug_notfound'], False) + self.assertEqual(settings['pyramid.debug_routematch'], False) + self.assertEqual(settings['pyramid.reload_templates'], False) + self.assertEqual(settings['pyramid.reload_resources'], False) + def test_prevent_http_cache(self): settings = self._makeOne({}) self.assertEqual(settings['prevent_http_cache'], False) + self.assertEqual(settings['pyramid.prevent_http_cache'], False) result = self._makeOne({'prevent_http_cache':'false'}) self.assertEqual(result['prevent_http_cache'], False) + self.assertEqual(result['pyramid.prevent_http_cache'], False) result = self._makeOne({'prevent_http_cache':'t'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'1'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'pyramid.prevent_http_cache':'t'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'false', 'pyramid.prevent_http_cache':'1'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'false', 'pyramid.prevent_http_cache':'f'}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) self.assertEqual(result['prevent_http_cache'], True) + self.assertEqual(result['pyramid.prevent_http_cache'], True) def test_reload_templates(self): settings = self._makeOne({}) self.assertEqual(settings['reload_templates'], False) + self.assertEqual(settings['pyramid.reload_templates'], False) result = self._makeOne({'reload_templates':'false'}) self.assertEqual(result['reload_templates'], False) + self.assertEqual(result['pyramid.reload_templates'], False) result = self._makeOne({'reload_templates':'t'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'pyramid.reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'false', 'pyramid.reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'false'}, {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) + self.assertEqual(result['pyramid.reload_templates'], True) def test_reload_resources(self): # alias for reload_assets result = self._makeOne({}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) + self.assertEqual(result['pyramid.reload_resources'], False) + self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_resources':'false'}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) + self.assertEqual(result['pyramid.reload_resources'], False) + self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_resources':'t'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'pyramid.reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'false', 'pyramid.reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'false', 'pyramid.reload_resources':'false'}, {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) def test_reload_assets(self): # alias for reload_resources result = self._makeOne({}) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['reload_resources'], False) + self.assertEqual(result['pyramid.reload_assets'], False) + self.assertEqual(result['pyramid.reload_resources'], False) result = self._makeOne({'reload_assets':'false'}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) + self.assertEqual(result['pyramid.reload_assets'], False) + self.assertEqual(result['pyramid.reload_resources'], False) result = self._makeOne({'reload_assets':'t'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'pyramid.reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'false', 'pyramid.reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'false', 'pyramid.reload_assets':'false'}, {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) - + self.assertEqual(result['pyramid.reload_assets'], True) + self.assertEqual(result['pyramid.reload_resources'], True) def test_reload_all(self): result = self._makeOne({}) self.assertEqual(result['reload_templates'], False) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) + self.assertEqual(result['pyramid.reload_templates'], False) + self.assertEqual(result['pyramid.reload_resources'], False) + self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_all':'false'}) self.assertEqual(result['reload_templates'], False) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) + self.assertEqual(result['pyramid.reload_templates'], False) + self.assertEqual(result['pyramid.reload_resources'], False) + self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_all':'t'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'pyramid.reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'false', 'pyramid.reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'false', 'pyramid.reload_all':'false'}, {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) + self.assertEqual(result['pyramid.reload_templates'], True) + self.assertEqual(result['pyramid.reload_resources'], True) + self.assertEqual(result['pyramid.reload_assets'], True) def test_debug_authorization(self): result = self._makeOne({}) self.assertEqual(result['debug_authorization'], False) + self.assertEqual(result['pyramid.debug_authorization'], False) result = self._makeOne({'debug_authorization':'false'}) self.assertEqual(result['debug_authorization'], False) + self.assertEqual(result['pyramid.debug_authorization'], False) result = self._makeOne({'debug_authorization':'t'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'pyramid.debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'false', 'pyramid.debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'false', 'pyramid.debug_authorization':'false'}, {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) def test_debug_notfound(self): result = self._makeOne({}) self.assertEqual(result['debug_notfound'], False) + self.assertEqual(result['pyramid.debug_notfound'], False) result = self._makeOne({'debug_notfound':'false'}) self.assertEqual(result['debug_notfound'], False) + self.assertEqual(result['pyramid.debug_notfound'], False) result = self._makeOne({'debug_notfound':'t'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'pyramid.debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'false', 'pyramid.debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'false', 'pyramid.debug_notfound':'false'}, {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) def test_debug_routematch(self): result = self._makeOne({}) self.assertEqual(result['debug_routematch'], False) + self.assertEqual(result['pyramid.debug_routematch'], False) result = self._makeOne({'debug_routematch':'false'}) self.assertEqual(result['debug_routematch'], False) + self.assertEqual(result['pyramid.debug_routematch'], False) result = self._makeOne({'debug_routematch':'t'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'pyramid.debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'false', 'pyramid.debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'false', 'pyramid.debug_routematch':'false'}, {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) def test_debug_templates(self): result = self._makeOne({}) self.assertEqual(result['debug_templates'], False) + self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_templates':'false'}) self.assertEqual(result['debug_templates'], False) + self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_templates':'t'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'pyramid.debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'false', 'pyramid.debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'false', 'pyramid.debug_templates':'false'}, {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_templates'], True) def test_debug_all(self): result = self._makeOne({}) @@ -257,37 +366,65 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['debug_templates'], False) + self.assertEqual(result['pyramid.debug_notfound'], False) + self.assertEqual(result['pyramid.debug_routematch'], False) + self.assertEqual(result['pyramid.debug_authorization'], False) + self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_all':'false'}) self.assertEqual(result['debug_notfound'], False) self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['debug_templates'], False) + self.assertEqual(result['pyramid.debug_notfound'], False) + self.assertEqual(result['pyramid.debug_routematch'], False) + self.assertEqual(result['pyramid.debug_authorization'], False) + self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_all':'t'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'pyramid.debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ALL':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'false', 'pyramid.debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'false', 'pyramid.debug_all':'false'}, {'PYRAMID_DEBUG_ALL':'1'}) @@ -295,23 +432,33 @@ class TestSettings(unittest.TestCase): self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) + self.assertEqual(result['pyramid.debug_notfound'], True) + self.assertEqual(result['pyramid.debug_routematch'], True) + self.assertEqual(result['pyramid.debug_authorization'], True) + self.assertEqual(result['pyramid.debug_templates'], True) def test_default_locale_name(self): result = self._makeOne({}) self.assertEqual(result['default_locale_name'], 'en') + self.assertEqual(result['pyramid.default_locale_name'], 'en') result = self._makeOne({'default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'pyramid.default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({}, {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'default_locale_name':'def', 'pyramid.default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'default_locale_name':'def', 'pyramid.default_locale_name':'ghi'}, {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') + self.assertEqual(result['pyramid.default_locale_name'], 'abc') def test_originals_kept(self): result = self._makeOne({'a':'i am so a'}) -- cgit v1.2.3 From 875ded31e7fdd0c85d1c91458248581b9dd729d7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 30 Jul 2011 01:50:24 -0600 Subject: Updated all of the docs to reflect the new pyramid.* settings prefix. --- docs/narr/MyProject/development.ini | 12 +- docs/narr/MyProject/production.ini | 12 +- docs/narr/commandline.rst | 12 +- docs/narr/environment.rst | 162 ++++++++++----------- docs/narr/hooks.rst | 8 +- docs/narr/i18n.rst | 18 +-- docs/narr/project.rst | 16 +- docs/narr/renderers.rst | 5 +- docs/narr/security.rst | 4 +- docs/narr/startup.rst | 12 +- docs/narr/templates.rst | 12 +- docs/narr/urldispatch.rst | 2 +- docs/narr/viewconfig.rst | 14 +- .../wiki/src/authorization/development.ini | 12 +- .../wiki/src/authorization/production.ini | 12 +- .../tutorials/wiki/src/basiclayout/development.ini | 12 +- docs/tutorials/wiki/src/basiclayout/production.ini | 12 +- docs/tutorials/wiki/src/models/development.ini | 12 +- docs/tutorials/wiki/src/models/production.ini | 12 +- docs/tutorials/wiki/src/tests/development.ini | 12 +- docs/tutorials/wiki/src/tests/production.ini | 12 +- docs/tutorials/wiki/src/views/development.ini | 12 +- docs/tutorials/wiki/src/views/production.ini | 12 +- docs/tutorials/wiki2/basiclayout.rst | 2 +- .../wiki2/src/authorization/development.ini | 12 +- .../wiki2/src/authorization/production.ini | 12 +- .../wiki2/src/basiclayout/development.ini | 12 +- .../tutorials/wiki2/src/basiclayout/production.ini | 12 +- docs/tutorials/wiki2/src/models/development.ini | 12 +- docs/tutorials/wiki2/src/models/production.ini | 12 +- docs/tutorials/wiki2/src/tests/development.ini | 12 +- docs/tutorials/wiki2/src/tests/production.ini | 12 +- docs/tutorials/wiki2/src/views/development.ini | 12 +- docs/tutorials/wiki2/src/views/production.ini | 12 +- 34 files changed, 266 insertions(+), 265 deletions(-) diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index 29486ce56..d0db3047c 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -1,11 +1,11 @@ [app:MyProject] use = egg:MyProject -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en [pipeline:main] pipeline = diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini index c1d0eee82..d0ed9628c 100644 --- a/docs/narr/MyProject/production.ini +++ b/docs/narr/MyProject/production.ini @@ -1,11 +1,11 @@ [app:MyProject] use = egg:MyProject -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en [filter:weberror] use = egg:WebError#error_catcher diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index f4cf951ba..509af7dd3 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -134,11 +134,11 @@ might have a ``[app:MyProject]`` section that looks like so: [app:MyProject] use = egg:MyProject - reload_templates = true - debug_authorization = false - debug_notfound = false - debug_templates = true - default_locale_name = en + pyramid.reload_templates = true + pyramid.debug_authorization = false + pyramid.debug_notfound = false + pyramid.debug_templates = true + pyramid.default_locale_name = en If so, you can use the following command to invoke a debug shell using the name ``MyProject`` as a section name: @@ -160,7 +160,7 @@ name ``MyProject`` as a section name: >>> registry - >>> registry.settings['debug_notfound'] + >>> registry.settings['pyramid.debug_notfound'] False >>> from myproject.views import my_view >>> from pyramid.request import Request diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 53234cba1..6465c2a1e 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -47,14 +47,14 @@ changes to templates take effect immediately during development. This flag is meaningful to Chameleon and Mako templates, as well as most third-party template rendering extensions. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_RELOAD_TEMPLATES`` | ``reload_templates`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+--------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+================================+ +| ``PYRAMID_RELOAD_TEMPLATES`` | ``pyramid.reload_templates`` | +| | | +| | | +| | | ++---------------------------------+--------------------------------+ Reloading Assets ---------------- @@ -65,7 +65,7 @@ also :ref:`overriding_assets_section`. +---------------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+=============================+ -| ``PYRAMID_RELOAD_ASSETS`` | ``reload_assets`` | +| ``PYRAMID_RELOAD_ASSETS`` | ``pyramid.reload_assets`` | | | | | | | | | | @@ -73,7 +73,7 @@ also :ref:`overriding_assets_section`. .. note:: For backwards compatibility purposes, aliases can be used for configurating asset reloading: ``PYRAMID_RELOAD_RESOURCES`` (envvar) - and ``reload_resources`` (config file). + and ``pyramid.reload_resources`` (config file). Debugging Authorization ----------------------- @@ -81,14 +81,14 @@ Debugging Authorization Print view authorization failure and success information to stderr when this value is true. See also :ref:`debug_authorization_section`. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+-----------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+===================================+ +| ``PYRAMID_DEBUG_AUTHORIZATION`` | ``pyramid.debug_authorization`` | +| | | +| | | +| | | ++---------------------------------+-----------------------------------+ Debugging Not Found Errors -------------------------- @@ -96,14 +96,14 @@ Debugging Not Found Errors Print view-related ``NotFound`` debug messages to stderr when this value is true. See also :ref:`debug_notfound_section`. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_DEBUG_NOTFOUND`` | ``debug_notfound`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+==============================+ +| ``PYRAMID_DEBUG_NOTFOUND`` | ``pyramid.debug_notfound`` | +| | | +| | | +| | | ++---------------------------------+------------------------------+ Debugging Route Matching ------------------------ @@ -111,14 +111,14 @@ Debugging Route Matching Print debugging messages related to :term:`url dispatch` route matching when this value is true. See also :ref:`debug_routematch_section`. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_DEBUG_ROUTEMATCH`` | ``debug_routematch`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+--------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+================================+ +| ``PYRAMID_DEBUG_ROUTEMATCH`` | ``pyramid.debug_routematch`` | +| | | +| | | +| | | ++---------------------------------+--------------------------------+ .. _preventing_http_caching: @@ -130,14 +130,14 @@ globally in this process when this value is true. No http caching-related response headers will be set by the Pyramid ``http_cache`` view configuration feature when this is true. See also :ref:`influencing_http_caching`. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_PREVENT_HTTP_CACHE`` | ``prevent_http_cache`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+----------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+==================================+ +| ``PYRAMID_PREVENT_HTTP_CACHE`` | ``pyramid.prevent_http_cache`` | +| | | +| | | +| | | ++---------------------------------+----------------------------------+ Debugging All ------------- @@ -147,7 +147,7 @@ Turns on all ``debug*`` settings. +---------------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+=============================+ -| ``PYRAMID_DEBUG_ALL`` | ``debug_all`` | +| ``PYRAMID_DEBUG_ALL`` | ``pyramid.debug_all`` | | | | | | | | | | @@ -161,7 +161,7 @@ Turns on all ``reload*`` settings. +---------------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+=============================+ -| ``PYRAMID_RELOAD_ALL`` | ``reload_all`` | +| ``PYRAMID_RELOAD_ALL`` | ``pyramid.reload_all`` | | | | | | | | | | @@ -176,14 +176,14 @@ The value supplied here is used as the default locale name when a :term:`locale negotiator` is not registered. See also :ref:`localization_deployment_settings`. -+---------------------------------+-----------------------------+ -| Environment Variable Name | Config File Setting Name | -+=================================+=============================+ -| ``PYRAMID_DEFAULT_LOCALE_NAME`` | ``default_locale_name`` | -| | | -| | | -| | | -+---------------------------------+-----------------------------+ ++---------------------------------+-----------------------------------+ +| Environment Variable Name | Config File Setting Name | ++=================================+===================================+ +| ``PYRAMID_DEFAULT_LOCALE_NAME`` | ``pyramid.default_locale_name`` | +| | | +| | | +| | | ++---------------------------------+-----------------------------------+ .. _mako_template_renderer_settings: @@ -346,8 +346,8 @@ an example of such a section: [app:main] use = egg:MyProject#app - reload_templates = true - debug_authorization = true + pyramid.reload_templates = true + pyramid.debug_authorization = true You can also use environment variables to accomplish the same purpose for settings documented as such. For example, you might start your @@ -364,18 +364,18 @@ respective settings in the ``[app:main]`` section of your application's ``.ini`` file. If you want to turn all ``debug`` settings (every setting that starts -with ``debug_``). on in one fell swoop, you can use +with ``pyramid.debug_``). on in one fell swoop, you can use ``PYRAMID_DEBUG_ALL=1`` as an environment variable setting or you may use -``debug_all=true`` in the config file. Note that this does not affect -settings that do not start with ``debug_*`` such as -``reload_templates``. +``pyramid.debug_all=true`` in the config file. Note that this does not affect +settings that do not start with ``pyramid.debug_*`` such as +``pyramid.reload_templates``. -If you want to turn all ``reload`` settings (every setting that starts -with ``reload_``) on in one fell swoop, you can use +If you want to turn all ``pyramid.reload`` settings (every setting that starts +with ``pyramid.reload_``) on in one fell swoop, you can use ``PYRAMID_RELOAD_ALL=1`` as an environment variable setting or you may use -``reload_all=true`` in the config file. Note that this does not -affect settings that do not start with ``reload_*`` such as -``debug_notfound``. +``pyramid.reload_all=true`` in the config file. Note that this does not +affect settings that do not start with ``pyramid.reload_*`` such as +``pyramid.debug_notfound``. .. note:: Specifying configuration settings via environment variables is generally @@ -392,35 +392,35 @@ affect settings that do not start with ``reload_*`` such as Understanding the Distinction Between ``reload_templates`` and ``reload_assets`` -------------------------------------------------------------------------------- -The difference between ``reload_assets`` and ``reload_templates`` is a bit -subtle. Templates are themselves also treated by :app:`Pyramid` as asset -files (along with other static files), so the distinction can be confusing. -It's helpful to read :ref:`overriding_assets_section` for some context -about assets in general. +The difference between ``pyramid.reload_assets`` and +``pyramid.reload_templates`` is a bit subtle. Templates are themselves also +treated by :app:`Pyramid` as asset files (along with other static files), so the +distinction can be confusing. It's helpful to read +:ref:`overriding_assets_section` for some context about assets in general. -When ``reload_templates`` is true, :app:`Pyramid` takes advantage of the +When ``pyramid.reload_templates`` is true, :app:`Pyramid` takes advantage of the underlying templating systems' ability to check for file modifications to an -individual template file. When ``reload_templates`` is true but -``reload_assets`` is *not* true, the template filename returned by the +individual template file. When ``pyramid.reload_templates`` is true but +``pyramid.reload_assets`` is *not* true, the template filename returned by the ``pkg_resources`` package (used under the hood by asset resolution) is cached by :app:`Pyramid` on the first request. Subsequent requests for the same template file will return a cached template filename. The underlying templating system checks for modifications to this particular file for every -request. Setting ``reload_templates`` to ``True`` doesn't affect performance -dramatically (although it should still not be used in production because it -has some effect). +request. Setting ``pyramid.reload_templates`` to ``True`` doesn't affect +performance dramatically (although it should still not be used in production +because it has some effect). -However, when ``reload_assets`` is true, :app:`Pyramid` will not cache the -template filename, meaning you can see the effect of changing the content of -an overridden asset directory for templates without restarting the server +However, when ``pyramid.reload_assets`` is true, :app:`Pyramid` will not cache +the template filename, meaning you can see the effect of changing the content +of an overridden asset directory for templates without restarting the server after every change. Subsequent requests for the same template file may return different filenames based on the current state of overridden asset -directories. Setting ``reload_assets`` to ``True`` affects performance +directories. Setting ``pyramid.reload_assets`` to ``True`` affects performance *dramatically*, slowing things down by an order of magnitude for each template rendering. However, it's convenient to enable when moving files -around in overridden asset directories. ``reload_assets`` makes the system -*very slow* when templates are in use. Never set ``reload_assets`` to -``True`` on a production system. +around in overridden asset directories. ``pyramid.reload_assets`` makes the +system *very slow* when templates are in use. Never set +``pyramid.reload_assets`` to ``True`` on a production system. .. index:: par: settings; adding custom diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index fc3f01271..4f493c854 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -61,8 +61,8 @@ Here's some sample code that implements a minimal NotFound view callable: caused the not found view to be called. The value of ``request.exception.message`` will be a value explaining why the not found error was raised. This message will be different when the - ``debug_notfound`` environment setting is true than it is when it is - false. + ``pyramid.debug_notfound`` environment setting is true than it is when it + is false. .. warning:: When a NotFound view callable accepts an argument list as described in :ref:`request_and_context_view_definitions`, the ``context`` @@ -128,8 +128,8 @@ Here's some sample code that implements a minimal forbidden view: ``request.exception.message`` will be a value explaining why the forbidden was raised and ``request.exception.result`` will be extended information about the forbidden exception. These messages will be different when the - ``debug_authorization`` environment setting is true than it is when it is - false. + ``pyramid.debug_authorization`` environment setting is true than it is when + it is false. .. index:: single: request factory diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 924fb047a..ba5490b31 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -623,7 +623,7 @@ You can obtain the locale name related to a request by using the This returns the locale name negotiated by the currently active :term:`locale negotiator` or the :term:`default locale name` if the locale negotiator returns ``None``. You can change the default locale -name by changing the ``default_locale_name`` setting; see +name by changing the ``pyramid.default_locale_name`` setting; see :ref:`default_locale_name_setting`. Once :func:`~pyramid.i18n.get_locale_name` is first run, the locale @@ -768,7 +768,7 @@ Internationalization" which explains how to add idiomatic I18N support to Localization-Related Deployment Settings ---------------------------------------- -A :app:`Pyramid` application will have a ``default_locale_name`` +A :app:`Pyramid` application will have a ``pyramid.default_locale_name`` setting. This value represents the :term:`default locale name` used when the :term:`locale negotiator` returns ``None``. Pass it to the :mod:`~pyramid.config.Configurator` constructor at startup @@ -778,9 +778,9 @@ time: :linenos: from pyramid.config import Configurator - config = Configurator(settings={'default_locale_name':'de'}) + config = Configurator(settings={'pyramid.default_locale_name':'de'}) -You may alternately supply a ``default_locale_name`` via an +You may alternately supply a ``pyramid.default_locale_name`` via an application's Paster ``.ini`` file: .. code-block:: ini @@ -788,10 +788,10 @@ application's Paster ``.ini`` file: [app:main] use = egg:MyProject#app - reload_templates = true - debug_authorization = false - debug_notfound = false - default_locale_name = de + pyramid.reload_templates = true + pyramid.debug_authorization = false + pyramid.debug_notfound = false + pyramid.default_locale_name = de If this value is not supplied via the Configurator constructor or via a Paste config file, it will default to ``en``. @@ -804,7 +804,7 @@ If this setting is supplied within the :app:`Pyramid` application from pyramid.threadlocal import get_current_registry settings = get_current_registry().settings - default_locale_name = settings['default_locale_name'] + default_locale_name = settings['pyramid.default_locale_name'] .. index:: single: detecting langauges diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 5f4878470..3b1b45eda 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -472,22 +472,22 @@ represented by this entry point (``main`` in our ``__init__.py`` module). You can provide startup-time configuration parameters to your application by adding more settings to this section. -The ``reload_templates`` setting in the ``[app:MyProject]`` section is a -:app:`Pyramid` -specific setting which is passed into the framework. If it +The ``pyramid.reload_templates`` setting in the ``[app:MyProject]`` section is +a :app:`Pyramid` -specific setting which is passed into the framework. If it exists, and its value is ``true``, :term:`Chameleon` and :term:`Mako` template changes will not require an application restart to be detected. See :ref:`reload_templates_section` for more information. -The ``debug_templates`` setting in the ``[app:MyProject]`` section is a +The ``pyramid.debug_templates`` setting in the ``[app:MyProject]`` section is a :app:`Pyramid` -specific setting which is passed into the framework. If it exists, and its value is ``true``, :term:`Chameleon` template exceptions will contain more detailed and helpful information about the error than when this value is ``false``. See :ref:`debug_templates_section` for more information. -.. warning:: The ``reload_templates`` and ``debug_templates`` options should - be turned off for production applications, as template rendering is slowed - when either is turned on. +.. warning:: The ``pyramid.reload_templates`` and ``pyramid.debug_templates`` + options should be turned off for production applications, as template + rendering is slowed when either is turned on. Various other settings may exist in this section having to do with debugging or influencing runtime behavior of a :app:`Pyramid` application. See @@ -795,14 +795,14 @@ file call to ``add_view``). See :ref:`views_which_use_a_renderer` for more information about how views, renderers, and templates relate and cooperate. -.. note:: Because our ``development.ini`` has a ``reload_templates = +.. note:: Because our ``development.ini`` has a ``pyramid.reload_templates = true`` directive indicating that templates should be reloaded when they change, you won't need to restart the application server to see changes you make to templates. During development, this is handy. If this directive had been ``false`` (or if the directive did not exist), you would need to restart the application server for each template change. For production applications, you should - set your project's ``reload_templates`` to ``false`` to increase + set your project's ``pyramid.reload_templates`` to ``false`` to increase the speed at which templates may be rendered. .. index:: diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 801741c43..ed391f4fe 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -425,8 +425,9 @@ The above configuration will use the file named ``foo.mak`` in the ``templates`` directory of the ``mypackage`` package. The ``Mako`` template renderer can take additional arguments beyond the -standard ``reload_templates`` setting, see the :ref:`environment_chapter` for -additional :ref:`mako_template_renderer_settings`. +standard ``pyramid.reload_templates`` setting, see the +:ref:`environment_chapter` for additional +:ref:`mako_template_renderer_settings`. .. index:: single: response headers (from a renderer) diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 65f3d7cf0..ce304ed9f 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -517,7 +517,7 @@ which ACL permitted or denied the authorization based on authentication information. This behavior can also be turned on in the application ``.ini`` file -by setting the ``debug_authorization`` key to ``true`` within the +by setting the ``pyramid.debug_authorization`` key to ``true`` within the application's configuration section, e.g.: .. code-block:: ini @@ -525,7 +525,7 @@ application's configuration section, e.g.: [app:main] use = egg:MyProject#app - debug_authorization = true + pyramid.debug_authorization = true With this debug flag turned on, the response sent to the browser will also contain security debugging information in its body. diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 68df9d417..c9ed01f83 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -92,10 +92,10 @@ Here's a high-level time-ordered overview of what happens when you press In this case, the ``myproject.__init__:main`` function referred to by the entry 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_routematch':'false', 'debug_templates':'true', - 'default_locale_name':'en'}``. + will receive the key/value pairs ``{'pyramid.reload_templates':'true', + 'pyramid.debug_authorization':'false', 'pyramid.debug_notfound':'false', + 'pyramid.debug_routematch':'false', 'pyramid.debug_templates':'true', + 'pyramid.default_locale_name':'en'}``. #. The ``main`` function first constructs a :class:`~pyramid.config.Configurator` instance, passing a root resource @@ -109,8 +109,8 @@ Here's a high-level time-ordered overview of what happens when you press The ``settings`` dictionary contains all the options in the ``[app:MyProject]`` section of our .ini file except the ``use`` option - (which is internal to Paste) such as ``reload_templates``, - ``debug_authorization``, etc. + (which is internal to Paste) such as ``pyramid.reload_templates``, + ``pyramid.debug_authorization``, etc. #. The ``main`` function then calls various methods on the instance of the class :class:`~pyramid.config.Configurator` created in the previous step. diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index af7257466..0f46f6422 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -632,15 +632,15 @@ variable set to ``1``, For example: $ PYRAMID_DEBUG_TEMPLATES=1 bin/paster serve myproject.ini To use a setting in the application ``.ini`` file for the same -purpose, set the ``debug_templates`` key to ``true`` within the -application's configuration section, e.g.: +purpose, set the ``pyramid.debug_templates`` key to ``true`` within +the application's configuration section, e.g.: .. code-block:: ini :linenos: [app:MyProject] use = egg:MyProject#app - debug_templates = true + pyramid.debug_templates = true With template debugging off, a :exc:`NameError` exception resulting from rendering a template with an undefined variable @@ -677,7 +677,7 @@ displaying the arguments passed to the template itself. .. note:: - Turning on ``debug_templates`` has the same effect as using the + Turning on ``pyramid.debug_templates`` has the same effect as using the Chameleon environment variable ``CHAMELEON_DEBUG``. See `Chameleon Environment Variables `_ @@ -793,7 +793,7 @@ variable set to ``1``, For example: $ PYRAMID_RELOAD_TEMPLATES=1 bin/paster serve myproject.ini To use a setting in the application ``.ini`` file for the same -purpose, set the ``reload_templates`` key to ``true`` within the +purpose, set the ``pyramid.reload_templates`` key to ``true`` within the application's configuration section, e.g.: .. code-block:: ini @@ -801,7 +801,7 @@ application's configuration section, e.g.: [app:main] use = egg:MyProject#app - reload_templates = true + pyramid.reload_templates = true .. index:: single: template system bindings diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 0598cd4f2..61c9770c6 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1005,7 +1005,7 @@ Debugging Route Matching It's useful to be able to take a peek under the hood when requests that enter your application arent matching your routes as you expect them to. To debug route matching, use the ``PYRAMID_DEBUG_ROUTEMATCH`` environment variable or the -``debug_routematch`` configuration file setting (set either to ``true``). +``pyramid.debug_routematch`` configuration file setting (set either to ``true``). Details of the route matching decision for a particular request to the :app:`Pyramid` application will be printed to the ``stderr`` of the console which you started the application from. For example: diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index d776887c8..a1b12ad2a 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -679,10 +679,10 @@ per :ref:`protecting_views`. It's useful to be able to debug :exc:`NotFound` error responses when they occur unexpectedly due to an application registry misconfiguration. To debug these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the -``debug_notfound`` configuration file setting. Details of why a view was not -found will be printed to ``stderr``, and the browser representation of the -error will include the same information. See :ref:`environment_chapter` for -more information about how, and where to set these values. +``pyramid.debug_notfound`` configuration file setting. Details of why a view +was not found will be printed to ``stderr``, and the browser representation of +the error will include the same information. See :ref:`environment_chapter` +for more information about how, and where to set these values. .. index:: single: HTTP caching @@ -729,10 +729,10 @@ headers you set within the view itself unless you use ``preserve_auto``. You can also turn of the effect of ``http_cache`` entirely for the duration of a Pyramid application lifetime. To do so, set the ``PYRAMID_PREVENT_HTTP_CACHE`` environment variable or the -``prevent_http_cache`` configuration value setting to a true value. For more -information, see :ref:`preventing_http_caching`. +``pyramid.prevent_http_cache`` configuration value setting to a true value. +For more information, see :ref:`preventing_http_caching`. -Note that setting ``prevent_http_cache`` will have no effect on caching +Note that setting ``pyramid.prevent_http_cache`` will have no effect on caching headers that your application code itself sets. It will only prevent caching headers that would have been set by the Pyramid HTTP caching machinery invoked as the result of the ``http_cache`` argument to view configuration. diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 1ba746d0e..07800514e 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini index 5c47ade9b..217bdd260 100644 --- a/docs/tutorials/wiki/src/authorization/production.ini +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 555010bed..04e341f2e 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini index 5c47ade9b..217bdd260 100644 --- a/docs/tutorials/wiki/src/basiclayout/production.ini +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 1ba746d0e..07800514e 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini index 5c47ade9b..217bdd260 100644 --- a/docs/tutorials/wiki/src/models/production.ini +++ b/docs/tutorials/wiki/src/models/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 1ba746d0e..07800514e 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini index 5c47ade9b..217bdd260 100644 --- a/docs/tutorials/wiki/src/tests/production.ini +++ b/docs/tutorials/wiki/src/tests/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 555010bed..04e341f2e 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini index 5c47ade9b..217bdd260 100644 --- a/docs/tutorials/wiki/src/views/production.ini +++ b/docs/tutorials/wiki/src/views/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 6151e0e25..0fd64fde4 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -57,7 +57,7 @@ The next step is to construct a :term:`Configurator`: ``settings`` is passed to the Configurator as a keyword argument with the dictionary values passed by PasteDeploy as the ``**settings`` argument. This will be a dictionary of settings parsed from the ``.ini`` file, which -contains deployment-related values such as ``reload_templates``, +contains deployment-related values such as ``pyramid.reload_templates``, ``db_string``, etc. We now can call :meth:`pyramid.config.Configurator.add_static_view` with the diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index 3b615f635..bd71cdba5 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index 0fdc38811..ed8eadacc 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [filter:weberror] diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index 3b615f635..bd71cdba5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index 0fdc38811..ed8eadacc 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [filter:weberror] diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index 3b615f635..bd71cdba5 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index 0fdc38811..ed8eadacc 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [filter:weberror] diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index 3b615f635..bd71cdba5 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index 0fdc38811..ed8eadacc 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [filter:weberror] diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index 3b615f635..bd71cdba5 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = true -default_locale_name = en +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = true +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index 0fdc38811..ed8eadacc 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -1,11 +1,11 @@ [app:tutorial] use = egg:tutorial -reload_templates = false -debug_authorization = false -debug_notfound = false -debug_routematch = false -debug_templates = false -default_locale_name = en +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.debug_templates = false +pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.db [filter:weberror] -- cgit v1.2.3 From ef6f6b8ce83288678db81cf528fbeb03db0b650e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 30 Jul 2011 04:33:42 -0400 Subject: note prefixes --- CHANGES.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 5ca9e9379..1f6da0d8b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -76,6 +76,16 @@ Features normal logging channels. The logger name of the debug logger will be the package name of the *caller* of the Configurator's constructor. +Deprecations +------------ + +- All Pyramid-related deployment settings (e.g. ``debug_all``, + ``debug_notfound``) are now meant to be prefixed with the prefix + ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The + old non-prefixed settings will continue to work indefinitely but supplying + them may print a deprecation warning. All scaffolds and tutorials have + been changed to use prefixed settings. + Backwards Incompatibilities --------------------------- -- cgit v1.2.3 From 2fcce796cf0caa4e036c3df81667cac4c58fe5b3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 30 Jul 2011 14:47:53 -0400 Subject: garden --- TODO.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO.txt b/TODO.txt index 761abdc58..fda59d7b4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -18,9 +18,6 @@ Should-Have during a config.include (they are related, so just exposing the currently underscored-private _set_auth* methods won't cut it). -- Rename all config file values with a ``pyramid.`` prefix. Preserve bw - compat, though, for older config files. - - Try to figure out a way to keep "settings" as the original dictionary passed to the Configurator instead of copying it. @@ -38,6 +35,9 @@ Should-Have ``paste.urlparser.StaticURLParser``, ``paste.auth.auth_tkt`` (cutnpaste or reimplement both). +- Use ``pyramid.include`` to add pyramid_debugtoolbar to all scaffolds and + remove weberror. + Nice-to-Have ------------ @@ -50,7 +50,7 @@ Nice-to-Have - Speed up startup time (defer _bootstrap and registerCommonDirectives() until needed by ZCML, as well as unfound speedups). -- Nicer Mako exceptions in WebError. +- Nicer Mako exceptions in debug toolbar. - Better "Extending" chapter. -- cgit v1.2.3 From 683d8583a70fb9bff44ad0f887565d80f3b2f6e8 Mon Sep 17 00:00:00 2001 From: Blaise Laflamme Date: Sun, 31 Jul 2011 01:53:26 -0400 Subject: fixed css scaffolds --- pyramid/scaffolds/alchemy/+package+/static/pylons.css | 2 +- pyramid/scaffolds/routesalchemy/+package+/static/pylons.css | 2 +- pyramid/scaffolds/starter/+package+/static/pylons.css | 2 +- pyramid/scaffolds/zodb/+package+/static/pylons.css | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/scaffolds/alchemy/+package+/static/pylons.css b/pyramid/scaffolds/alchemy/+package+/static/pylons.css index d952fb42e..c54499ddd 100644 --- a/pyramid/scaffolds/alchemy/+package+/static/pylons.css +++ b/pyramid/scaffolds/alchemy/+package+/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, diff --git a/pyramid/scaffolds/routesalchemy/+package+/static/pylons.css b/pyramid/scaffolds/routesalchemy/+package+/static/pylons.css index d952fb42e..c54499ddd 100644 --- a/pyramid/scaffolds/routesalchemy/+package+/static/pylons.css +++ b/pyramid/scaffolds/routesalchemy/+package+/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, diff --git a/pyramid/scaffolds/starter/+package+/static/pylons.css b/pyramid/scaffolds/starter/+package+/static/pylons.css index d952fb42e..c54499ddd 100644 --- a/pyramid/scaffolds/starter/+package+/static/pylons.css +++ b/pyramid/scaffolds/starter/+package+/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, diff --git a/pyramid/scaffolds/zodb/+package+/static/pylons.css b/pyramid/scaffolds/zodb/+package+/static/pylons.css index d952fb42e..c54499ddd 100644 --- a/pyramid/scaffolds/zodb/+package+/static/pylons.css +++ b/pyramid/scaffolds/zodb/+package+/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, -- cgit v1.2.3 From 449c2ddc4245eb1457393acdaed122c4d4fb3fa7 Mon Sep 17 00:00:00 2001 From: Blaise Laflamme Date: Sun, 31 Jul 2011 02:02:52 -0400 Subject: updated wiki tutorials css --- docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css | 4 ++-- .../tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki/src/models/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki/src/tests/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki/src/views/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css | 4 ++-- .../wiki2/src/authorization/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki2/src/models/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt | 3 ++- docs/tutorials/wiki2/src/views/tutorial/static/pylons.css | 4 ++-- docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt | 3 ++- 20 files changed, 40 insertions(+), 30 deletions(-) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index c2bc1fd65..f9f351c97 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css index a9f49cc85..7e6ec739d 100644 --- a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css b/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/tests/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/tests/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css index fd1914d8d..c54499ddd 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css @@ -23,7 +23,7 @@ h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,7 +31,7 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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;} diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt index efe581b59..14b88d16a 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt @@ -6,8 +6,9 @@ + + - -- cgit v1.2.3 From dc27f6ccd181f553da1c2ef0debb5ecc355012a9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 31 Jul 2011 03:23:18 -0400 Subject: garden --- TODO.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.txt b/TODO.txt index fda59d7b4..ffa40ade1 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,8 @@ Pyramid TODOs Should-Have ----------- +- Add request.exc_info during an exception. + - Merge https://github.com/Pylons/pyramid/pull/242 (IPython update; requires test fixes and additional test coverage). -- cgit v1.2.3 From 95a3791409f4a936c47e7018e75f14fc3b701380 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 31 Jul 2011 03:57:06 -0400 Subject: - A new attribute is available on request objects: ``exc_info``. Its value will be ``None`` until an exception is caught by the Pyramid router, after which it will be the result of ``sys.exc_info()``. --- CHANGES.txt | 4 ++++ TODO.txt | 2 -- docs/api/request.rst | 11 +++++++++++ pyramid/request.py | 1 + pyramid/router.py | 4 ++++ pyramid/tests/test_router.py | 3 ++- 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1f6da0d8b..c94ecb800 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -76,6 +76,10 @@ Features normal logging channels. The logger name of the debug logger will be the package name of the *caller* of the Configurator's constructor. +- A new attribute is available on request objects: ``exc_info``. Its value + will be ``None`` until an exception is caught by the Pyramid router, after + which it will be the result of ``sys.exc_info()``. + Deprecations ------------ diff --git a/TODO.txt b/TODO.txt index ffa40ade1..fda59d7b4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,8 +4,6 @@ Pyramid TODOs Should-Have ----------- -- Add request.exc_info during an exception. - - Merge https://github.com/Pylons/pyramid/pull/242 (IPython update; requires test fixes and additional test coverage). diff --git a/docs/api/request.rst b/docs/api/request.rst index 404825d1b..2ab3977d5 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -85,6 +85,17 @@ of ``request.exception`` will be ``None`` within response and finished callbacks. + .. attribute:: exc_info + + If an exception was raised by a :term:`root factory` or a :term:`view + callable`, or at various other points where :app:`Pyramid` executes + user-defined code during the processing of a request, result of + ``sys.exc_info()`` will be available as the ``exc_info`` attribute of + the request within a :term:`exception view`, a :term:`response callback` + or a :term:`finished callback`. If no exception occurred, the value of + ``request.exc_info`` will be ``None`` within response and finished + callbacks. + .. attribute:: response This attribute is actually a "reified" property which returns an diff --git a/pyramid/request.py b/pyramid/request.py index 8df204681..2a654d218 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -204,6 +204,7 @@ class Request(BaseRequest, DeprecatedRequestMethods): response_callbacks = () finished_callbacks = () exception = None + exc_info = None matchdict = None matched_route = None diff --git a/pyramid/router.py b/pyramid/router.py index efbf0b326..aa9e4a1ba 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,3 +1,5 @@ +import sys + from zope.interface import implements from zope.interface import providedBy @@ -166,6 +168,7 @@ class Router(object): # handle exceptions raised during root finding and view-exec except Exception, why: + exc_info = sys.exc_info() # clear old generated request.response, if any; it may # have been mutated by the view, and its state is not # sane (e.g. caching headers) @@ -173,6 +176,7 @@ class Router(object): del attrs['response'] attrs['exception'] = why + attrs['exc_info'] = exc_info for_ = (IExceptionViewClassifier, request_iface.combined, diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 4d44de5c0..e44465992 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -808,7 +808,8 @@ class TestRouter(unittest.TestCase): # ``exception`` must be attached to request even if a suitable # exception view cannot be found self.assertEqual(request.exception.__class__, RuntimeError) - + self.assertEqual(request.exc_info[0], RuntimeError) + def test_call_view_raises_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier -- cgit v1.2.3 From be03f74bea45854fec8aabca9407ca983c65111f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 31 Jul 2011 09:35:53 -0600 Subject: Fixed a spelling mistake in ISession. --- pyramid/interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index a06cb7e52..5ef11b1cf 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -777,13 +777,13 @@ class ISession(Interface): """ Pop a queue from the flash storage. The queue is removed from flash storage after this message is called. The queue is returned; it is a list of flash messages added by - :meth:`pyramid.interfaces.ISesssion.flash`""" + :meth:`pyramid.interfaces.ISession.flash`""" def peek_flash(queue=''): """ Peek at a queue in the flash storage. The queue remains in flash storage after this message is called. The queue is returned; it is a list of flash messages added by - :meth:`pyramid.interfaces.ISesssion.flash` + :meth:`pyramid.interfaces.ISession.flash` """ def new_csrf_token(): -- cgit v1.2.3 From c337a8477658e9a41a07fa3d0a17c23bde93f26c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 31 Jul 2011 09:46:02 -0600 Subject: Fixed spelling in the IRoute interface. --- pyramid/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 5ef11b1cf..41e896adf 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -631,7 +631,7 @@ class IRoute(Interface): 'when this route matches (or ``None``)') predicates = Attribute( 'A sequence of :term:`route predicate` objects used to ' - 'determine if a request matches this route or not or not after ' + 'determine if a request matches this route or not after ' 'basic pattern matching has been completed.') pregenerator = Attribute('This attribute should either be ``None`` or ' 'a callable object implementing the ' -- cgit v1.2.3 From 01f9289b99be03eb203afcd64b010f3455f5db33 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 31 Jul 2011 23:54:56 -0600 Subject: Removed unused PCommand testing hooks. --- pyramid/paster.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 277f093d7..ca343def7 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -87,11 +87,6 @@ def bootstrap(config_uri, request=None): _marker = object() class PCommand(Command): - get_app = staticmethod(get_app) # hook point - get_root = staticmethod(get_root) # hook point - group_name = 'pyramid' - interact = (interact,) # for testing - loadapp = (loadapp,) # for testing bootstrap = (bootstrap,) # testing verbose = 3 -- cgit v1.2.3 From ba06e145659f95353621fb1b4cfed9593068664c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 Aug 2011 00:43:10 -0600 Subject: Refactored the PShellCommand to improve testability. --- pyramid/paster.py | 70 ++++++++++++-------- pyramid/tests/test_paster.py | 154 ++++++++++++++++++++++++++++++------------- 2 files changed, 152 insertions(+), 72 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index ca343def7..3143fa91e 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -84,8 +84,6 @@ def bootstrap(config_uri, request=None): env['app'] = app return env -_marker = object() - class PCommand(Command): bootstrap = (bootstrap,) # testing verbose = 3 @@ -143,8 +141,7 @@ class PShellCommand(PCommand): self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v - def command(self, IPShell=_marker): - # IPShell passed to command method is for testing purposes + def command(self, shell=None): config_uri = self.args[0] config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) @@ -185,33 +182,52 @@ class PShellCommand(PCommand): for var in sorted(self.object_help.keys()): help += '\n %-12s %s' % (var, self.object_help[var]) - help += '\n' - - if IPShell is _marker: - try: #pragma no cover - try: #pragma no cover - from IPython.frontend.terminal.embed import InteractiveShellEmbed - IPShell = InteractiveShellEmbed(banner2=help, user_ns=env) - except ImportError: #pragma no cover - from IPython.Shell import IPShellEmbed - IPShell = IPShellEmbed(argv=[], user_ns=env) - IPShell.set_banner(IPShell.IP.BANNER + '\n' + help) - except ImportError: #pragma no cover - IPShell = None - - if (IPShell is None) or self.options.disable_ipython: + if shell is None and not self.options.disable_ipython: + shell = self.make_ipython_v0_11_shell() + if shell is None: + shell = self.make_ipython_v0_10_shell() + + if shell is None: + shell = self.make_default_shell() + + try: + shell(env, help) + finally: + closer() + + def make_default_shell(self, interact=interact): + def shell(env, help): cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - banner += '\n' + help + banner += '\n' + help + '\n' + interact(banner, local=env) + return shell + + def make_ipython_v0_11_shell(self, IPShellFactory=None): + if IPShellFactory is None: # pragma: no cover try: - self.interact[0](banner, local=env) - finally: - closer() - else: + from IPython.frontend.terminal.embed import ( + InteractiveShellEmbed) + IPShellFactory = InteractiveShellEmbed + except ImportError: + return None + def shell(env, help): + IPShell = IPShellFactory(banner2=help, user_ns=env) + IPShell() + return shell + + def make_ipython_v0_10_shell(self, IPShellFactory=None): + if IPShellFactory is None: # pragma: no cover try: - IPShell() - finally: - closer() + from IPython.Shell import IPShellEmbed + IPShellFactory = IPShellEmbed + except ImportError: + return None + def shell(env, help): + IPShell = IPShellFactory(argv=[], user_ns=env) + IPShell.set_banner(IPShell.IP.BANNER + '\n' + help + '\n') + IPShell() + return shell BFGShellCommand = PShellCommand # b/w compat forever diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 085cda810..3cf249c5c 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -5,12 +5,9 @@ class TestPShellCommand(unittest.TestCase): from pyramid.paster import PShellCommand return PShellCommand - def _makeOne(self, patch_interact=True, patch_bootstrap=True, - patch_config=True, patch_args=True, patch_options=True): + def _makeOne(self, patch_bootstrap=True, patch_config=True, + patch_args=True, patch_options=True): cmd = self._getTargetClass()('pshell') - if patch_interact: - self.interact = DummyInteractor() - cmd.interact = (self.interact,) if patch_bootstrap: self.bootstrap = DummyBootstrap() cmd.bootstrap = (self.bootstrap,) @@ -27,69 +24,129 @@ class TestPShellCommand(unittest.TestCase): cmd.options = self.options return cmd - def test_command_ipshell_is_None_ipython_enabled(self): + def test_make_default_shell(self): command = self._makeOne() - command.options.disable_ipython = True - command.command(IPShell=None) + interact = DummyInteractor() + shell = command.make_default_shell(interact) + shell({'foo': 'bar'}, 'a help message') + self.assertEqual(interact.local, {'foo': 'bar'}) + self.assertTrue('a help message' in interact.banner) + + def test_make_ipython_v0_11_shell(self): + command = self._makeOne() + ipshell_factory = DummyIPShellFactory() + shell = command.make_ipython_v0_11_shell(ipshell_factory) + shell({'foo': 'bar'}, 'a help message') + self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) + self.assertTrue('a help message' in ipshell_factory.kw['banner2']) + self.assertTrue(ipshell_factory.shell.called) + + def test_make_ipython_v0_10_shell(self): + command = self._makeOne() + ipshell_factory = DummyIPShellFactory() + shell = command.make_ipython_v0_10_shell(ipshell_factory) + shell({'foo': 'bar'}, 'a help message') + self.assertEqual(ipshell_factory.kw['argv'], []) + self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) + self.assertTrue('a help message' in ipshell_factory.shell.banner) + self.assertTrue(ipshell_factory.shell.called) + + def test_command_loads_default_shell(self): + command = self._makeOne() + shell = DummyShell() + command.make_ipython_v0_11_shell = lambda: None + command.make_ipython_v0_10_shell = lambda: None + command.make_default_shell = lambda: shell + command.command() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(self.interact.local, { + self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, }) self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(self.interact.banner) + self.assertTrue(shell.help) - def test_command_ipshell_is_not_None_ipython_disabled(self): + def test_command_loads_default_shell_with_ipython_disabled(self): command = self._makeOne() + shell = DummyShell() + bad_shell = DummyShell() + command.make_ipython_v0_11_shell = lambda: bad_shell + command.make_ipython_v0_10_shell = lambda: bad_shell + command.make_default_shell = lambda: shell command.options.disable_ipython = True - command.command(IPShell='notnone') + command.command() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(self.interact.local, { + self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, }) + self.assertEqual(bad_shell.env, {}) self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(self.interact.banner) + self.assertTrue(shell.help) - def test_command_ipython_enabled(self): - command = self._makeOne(patch_interact=False) + def test_command_loads_ipython_v0_11(self): + command = self._makeOne() + shell = DummyShell() + command.make_ipython_v0_11_shell = lambda: shell + command.make_ipython_v0_10_shell = lambda: None + command.make_default_shell = lambda: None command.options.disable_ipython = False - dummy_shell_factory = DummyIPShellFactory() - command.command(IPShell=dummy_shell_factory) + command.command() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(dummy_shell_factory.shell.local_ns, { + self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, }) - self.assertEqual(dummy_shell_factory.shell.global_ns, {}) self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + + def test_command_loads_ipython_v0_10(self): + command = self._makeOne() + shell = DummyShell() + command.make_ipython_v0_11_shell = lambda: None + command.make_ipython_v0_10_shell = lambda: shell + command.make_default_shell = lambda: None + command.options.disable_ipython = False + command.command() + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) def test_command_loads_custom_items(self): command = self._makeOne() model = Dummy() self.config_factory.items = [('m', model)] - command.options.disable_ipython = True - command.command(IPShell=None) + shell = DummyShell() + command.command(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(self.interact.local, { + self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, @@ -97,25 +154,25 @@ class TestPShellCommand(unittest.TestCase): 'm':model, }) self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(self.interact.banner) + self.assertTrue(shell.help) def test_command_custom_section_override(self): command = self._makeOne() dummy = Dummy() self.config_factory.items = [('app', dummy), ('root', dummy), ('registry', dummy), ('request', dummy)] - command.options.disable_ipython = True - command.command(IPShell=None) + shell = DummyShell() + command.command(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(self.interact.local, { + self.assertEqual(shell.env, { 'app':dummy, 'root':dummy, 'registry':dummy, 'request':dummy, 'root_factory':self.bootstrap.root_factory, }) self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(self.interact.banner) + self.assertTrue(shell.help) class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): @@ -770,23 +827,6 @@ class TestBootstrap(unittest.TestCase): class Dummy: pass -class DummyIPShellFactory(object): - def __call__(self, argv, user_ns=None): - shell = DummyIPShell() - shell(user_ns, {}) - self.shell = shell - return shell - -class DummyIPShell(object): - IP = Dummy() - IP.BANNER = 'foo' - def __call__(self, local_ns, global_ns): - self.local_ns = local_ns - self.global_ns = global_ns - - def mainloop(self): - pass - dummy_root = Dummy() class DummyRegistry(object): @@ -796,11 +836,35 @@ class DummyRegistry(object): dummy_registry = DummyRegistry() +class DummyShell(object): + env = {} + help = '' + + def __call__(self, env, help): + self.env = env + self.help = help + class DummyInteractor: def __call__(self, banner, local): self.banner = banner self.local = local +class DummyIPShell(object): + IP = Dummy() + IP.BANNER = 'foo' + + def set_banner(self, banner): + self.banner = banner + + def __call__(self): + self.called = True + +class DummyIPShellFactory(object): + def __call__(self, **kw): + self.kw = kw + self.shell = DummyIPShell() + return self.shell + class DummyLoadApp: def __init__(self, app): self.app = app -- cgit v1.2.3 From 6456c2d69727369826b3cc31e168704a402bab89 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 2 Aug 2011 08:45:20 -0400 Subject: avoid memory leak potential when assigning sys.exc_info() result to an attr of the request --- pyramid/router.py | 9 +++++---- pyramid/tests/test_router.py | 13 +++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index aa9e4a1ba..ddec23cdb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -168,7 +168,6 @@ class Router(object): # handle exceptions raised during root finding and view-exec except Exception, why: - exc_info = sys.exc_info() # clear old generated request.response, if any; it may # have been mutated by the view, and its state is not # sane (e.g. caching headers) @@ -176,7 +175,7 @@ class Router(object): del attrs['response'] attrs['exception'] = why - attrs['exc_info'] = exc_info + attrs['exc_info'] = sys.exc_info() for_ = (IExceptionViewClassifier, request_iface.combined, @@ -217,8 +216,10 @@ class Router(object): request.registry = registry response = self.handle_request(request) finally: - if request is not None and request.finished_callbacks: - request._process_finished_callbacks() + if request is not None: + if request.finished_callbacks: + request._process_finished_callbacks() + request.exc_info = None # avoid leak return response(request.environ, start_response) finally: diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index e44465992..b943f1ee6 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -790,6 +790,7 @@ class TestRouter(unittest.TestCase): from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequestFactory + from pyramid.interfaces import IExceptionViewClassifier def rfactory(environ): return request self.registry.registerUtility(rfactory, IRequestFactory) @@ -799,16 +800,24 @@ class TestRouter(unittest.TestCase): directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() + response.app_iter = ['OK'] view = DummyView(response, raise_exception=RuntimeError) environ = self._makeEnviron() + def exception_view(context, request): + self.assertEqual(request.exc_info[0], RuntimeError) + return response self._registerView(view, '', IViewClassifier, IRequest, IContext) + self._registerView(exception_view, '', IExceptionViewClassifier, + IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() - self.assertRaises(RuntimeError, router, environ, start_response) + result = router(environ, start_response) + self.assertEqual(result, ['OK']) # ``exception`` must be attached to request even if a suitable # exception view cannot be found self.assertEqual(request.exception.__class__, RuntimeError) - self.assertEqual(request.exc_info[0], RuntimeError) + # we clean up the exc_info after the request + self.assertEqual(request.exc_info, None) def test_call_view_raises_exception_view(self): from pyramid.interfaces import IViewClassifier -- cgit v1.2.3 From d15920e8e4ebf90e0213ad79017f621ea0e9781e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 2 Aug 2011 21:10:32 -0400 Subject: first cut --- pyramid/router.py | 286 +++++++++++++++++++++++++++--------------------------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index ddec23cdb..73e97a94b 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -57,143 +57,160 @@ class Router(object): self.debug_routematch = settings['debug_routematch'] def handle_request(self, request): + exc = None attrs = request.__dict__ registry = attrs['registry'] - request_iface = IRequest - context = None - routes_mapper = self.routes_mapper - debug_routematch = self.debug_routematch - adapters = registry.adapters - has_listeners = registry.has_listeners - notify = registry.notify - logger = self.logger - - try: # matches except Exception (exception view execution) - has_listeners and notify(NewRequest(request)) - # find the root object - root_factory = self.root_factory - if routes_mapper is not None: - info = routes_mapper(request) - match, route = info['match'], info['route'] - if route is None: - if debug_routematch: - msg = ('no route matched for url %s' % - request.url) - logger and logger.debug(msg) - else: - # TODO: kill off bfg.routes.* environ keys - # when traverser requires request arg, and - # cant cope with environ anymore (they are - # docs-deprecated as of BFG 1.3) - environ = request.environ - environ['bfg.routes.route'] = route - environ['bfg.routes.matchdict'] = match - attrs['matchdict'] = match - attrs['matched_route'] = route - - if debug_routematch: + + try: # matches finally: manager.pop() + manager = self.threadlocal_manager + threadlocals = { 'registry':registry, 'request':request} + manager.push(threadlocals) + request_iface = IRequest + context = None + routes_mapper = self.routes_mapper + debug_routematch = self.debug_routematch + adapters = registry.adapters + has_listeners = registry.has_listeners + notify = registry.notify + logger = self.logger + + try: # matches except Exception (exception view execution) + has_listeners and notify(NewRequest(request)) + # find the root object + root_factory = self.root_factory + if routes_mapper is not None: + info = routes_mapper(request) + match, route = info['match'], info['route'] + if route is None: + if debug_routematch: + msg = ('no route matched for url %s' % + request.url) + logger and logger.debug(msg) + else: + # TODO: kill off bfg.routes.* environ keys + # when traverser requires request arg, and + # cant cope with environ anymore (they are + # docs-deprecated as of BFG 1.3) + environ = request.environ + environ['bfg.routes.route'] = route + environ['bfg.routes.matchdict'] = match + attrs['matchdict'] = match + attrs['matched_route'] = route + + if debug_routematch: + msg = ( + 'route matched for url %s; ' + 'route_name: %r, ' + 'path_info: %r, ' + 'pattern: %r, ' + 'matchdict: %r, ' + 'predicates: %r' % ( + request.url, + route.name, + request.path_info, + route.pattern, match, + route.predicates) + ) + logger and logger.debug(msg) + + request_iface = registry.queryUtility( + IRouteRequest, + name=route.name, + default=IRequest) + + root_factory = route.factory or \ + self.root_factory + + root = root_factory(request) + attrs['root'] = root + + # find a context + traverser = adapters.queryAdapter(root, ITraverser) + if traverser is None: + traverser = ResourceTreeTraverser(root) + tdict = traverser(request) + + context, view_name, subpath, traversed, vroot, \ + vroot_path = ( + tdict['context'], + tdict['view_name'], + tdict['subpath'], + tdict['traversed'], + tdict['virtual_root'], + tdict['virtual_root_path'] + ) + + attrs.update(tdict) + has_listeners and notify(ContextFound(request)) + + # find a view callable + context_iface = providedBy(context) + view_callable = adapters.lookup( + (IViewClassifier, request_iface, context_iface), + IView, name=view_name, default=None) + + # invoke the view callable + if view_callable is None: + if self.debug_notfound: msg = ( - 'route matched for url %s; ' - 'route_name: %r, ' - 'path_info: %r, ' - 'pattern: %r, ' - 'matchdict: %r, ' - 'predicates: %r' % ( - request.url, - route.name, - request.path_info, - route.pattern, match, - route.predicates) + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, + subpath, traversed, root, vroot, + vroot_path) ) logger and logger.debug(msg) - - request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) - - root_factory = route.factory or \ - self.root_factory - - root = root_factory(request) - attrs['root'] = root - - # find a context - traverser = adapters.queryAdapter(root, ITraverser) - if traverser is None: - traverser = ResourceTreeTraverser(root) - tdict = traverser(request) - - context, view_name, subpath, traversed, vroot, \ - vroot_path = ( - tdict['context'], - tdict['view_name'], - tdict['subpath'], - tdict['traversed'], - tdict['virtual_root'], - tdict['virtual_root_path'] - ) - - attrs.update(tdict) - has_listeners and notify(ContextFound(request)) - - # find a view callable - context_iface = providedBy(context) - view_callable = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name=view_name, default=None) - - # invoke the view callable - if view_callable is None: - if self.debug_notfound: - msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, - subpath, traversed, root, vroot, - vroot_path) - ) - logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) else: - msg = request.path_info - raise HTTPNotFound(msg) - else: - # if there were any view wrappers for the current - # request, use them to wrap the view + # if there were any view wrappers for the current + # request, use them to wrap the view - response = view_callable(context, request) + response = view_callable(context, request) - # handle exceptions raised during root finding and view-exec - except Exception, why: - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] + # handle exceptions raised during root finding and view-exec + except Exception, exc: + # WARNING: do not assign the result of sys.exc_info() to a + # local var here, doing so will cause a leak + attrs['exc_info'] = sys.exc_info() + attrs['exception'] = exc - attrs['exception'] = why - attrs['exc_info'] = sys.exc_info() + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] - for_ = (IExceptionViewClassifier, - request_iface.combined, - providedBy(why)) - view_callable = adapters.lookup(for_, IView, - default=None) + for_ = (IExceptionViewClassifier, + request_iface.combined, + providedBy(exc)) + view_callable = adapters.lookup(for_, IView, + default=None) - if view_callable is None: - raise + if view_callable is None: + raise - response = view_callable(why, request) + response = view_callable(exc, request) - has_listeners and notify(NewResponse(request, response)) + has_listeners and notify(NewResponse(request, response)) - if request.response_callbacks: - request._process_response_callbacks(response) + if request.response_callbacks: + request._process_response_callbacks(response) - return response + return request, response, attrs.get('exc_info') + + finally: + manager.pop() + if request is not None: + try: + if request.finished_callbacks: + request._process_finished_callbacks() + finally: + request.exc_info = None def __call__(self, environ, start_response): """ @@ -203,25 +220,8 @@ class Router(object): within the application registry; call ``start_response`` and return an iterable. """ - registry = self.registry - manager = self.threadlocal_manager - request = None - threadlocals = {'registry':registry, 'request':request} - manager.push(threadlocals) - - try: - try: - request = self.request_factory(environ) - threadlocals['request'] = request - request.registry = registry - response = self.handle_request(request) - finally: - if request is not None: - if request.finished_callbacks: - request._process_finished_callbacks() - request.exc_info = None # avoid leak - - return response(request.environ, start_response) - finally: - manager.pop() + request = self.request_factory(environ) + request.registry = self.registry + request, response = self.handle_request(request)[:2] + return response(request.environ, start_response) -- cgit v1.2.3 From 3c4555ea118884bf0fc3636b5c6e3f851c52ce41 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Aug 2011 05:57:58 -0400 Subject: refactor --- pyramid/router.py | 272 +++++++++++++++++++++++++++--------------------------- 1 file changed, 138 insertions(+), 134 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 73e97a94b..5cc144996 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -48,6 +48,9 @@ class Router(object): name=name) handler = handler_factory(handler, registry) self.handle_request = handler + else: + self.handle_request = exception_view_handler_factory( + self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings @@ -57,15 +60,11 @@ class Router(object): self.debug_routematch = settings['debug_routematch'] def handle_request(self, request): - exc = None attrs = request.__dict__ registry = attrs['registry'] - try: # matches finally: manager.pop() - manager = self.threadlocal_manager - threadlocals = { 'registry':registry, 'request':request} - manager.push(threadlocals) - request_iface = IRequest + try: # matches finally: if request is not None + request.request_iface = IRequest context = None routes_mapper = self.routes_mapper debug_routematch = self.debug_routematch @@ -74,143 +73,107 @@ class Router(object): notify = registry.notify logger = self.logger - try: # matches except Exception (exception view execution) - has_listeners and notify(NewRequest(request)) - # find the root object - root_factory = self.root_factory - if routes_mapper is not None: - info = routes_mapper(request) - match, route = info['match'], info['route'] - if route is None: - if debug_routematch: - msg = ('no route matched for url %s' % - request.url) - logger and logger.debug(msg) - else: - # TODO: kill off bfg.routes.* environ keys - # when traverser requires request arg, and - # cant cope with environ anymore (they are - # docs-deprecated as of BFG 1.3) - environ = request.environ - environ['bfg.routes.route'] = route - environ['bfg.routes.matchdict'] = match - attrs['matchdict'] = match - attrs['matched_route'] = route - - if debug_routematch: - msg = ( - 'route matched for url %s; ' - 'route_name: %r, ' - 'path_info: %r, ' - 'pattern: %r, ' - 'matchdict: %r, ' - 'predicates: %r' % ( - request.url, - route.name, - request.path_info, - route.pattern, match, - route.predicates) - ) - logger and logger.debug(msg) - - request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) - - root_factory = route.factory or \ - self.root_factory - - root = root_factory(request) - attrs['root'] = root - - # find a context - traverser = adapters.queryAdapter(root, ITraverser) - if traverser is None: - traverser = ResourceTreeTraverser(root) - tdict = traverser(request) - - context, view_name, subpath, traversed, vroot, \ - vroot_path = ( - tdict['context'], - tdict['view_name'], - tdict['subpath'], - tdict['traversed'], - tdict['virtual_root'], - tdict['virtual_root_path'] - ) - - attrs.update(tdict) - has_listeners and notify(ContextFound(request)) - - # find a view callable - context_iface = providedBy(context) - view_callable = adapters.lookup( - (IViewClassifier, request_iface, context_iface), - IView, name=view_name, default=None) - - # invoke the view callable - if view_callable is None: - if self.debug_notfound: + has_listeners and notify(NewRequest(request)) + # find the root object + root_factory = self.root_factory + if routes_mapper is not None: + info = routes_mapper(request) + match, route = info['match'], info['route'] + if route is None: + if debug_routematch: + msg = ('no route matched for url %s' % + request.url) + logger and logger.debug(msg) + else: + # TODO: kill off bfg.routes.* environ keys + # when traverser requires request arg, and + # cant cope with environ anymore (they are + # docs-deprecated as of BFG 1.3) + environ = request.environ + environ['bfg.routes.route'] = route + environ['bfg.routes.matchdict'] = match + attrs['matchdict'] = match + attrs['matched_route'] = route + + if debug_routematch: msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, - subpath, traversed, root, vroot, - vroot_path) + 'route matched for url %s; ' + 'route_name: %r, ' + 'path_info: %r, ' + 'pattern: %r, ' + 'matchdict: %r, ' + 'predicates: %r' % ( + request.url, + route.name, + request.path_info, + route.pattern, match, + route.predicates) ) logger and logger.debug(msg) - else: - msg = request.path_info - raise HTTPNotFound(msg) - else: - # if there were any view wrappers for the current - # request, use them to wrap the view - - response = view_callable(context, request) - - # handle exceptions raised during root finding and view-exec - except Exception, exc: - # WARNING: do not assign the result of sys.exc_info() to a - # local var here, doing so will cause a leak - attrs['exc_info'] = sys.exc_info() - attrs['exception'] = exc - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] - - for_ = (IExceptionViewClassifier, - request_iface.combined, - providedBy(exc)) - view_callable = adapters.lookup(for_, IView, - default=None) - - if view_callable is None: - raise - - response = view_callable(exc, request) + request.request_iface = registry.queryUtility( + IRouteRequest, + name=route.name, + default=IRequest) + + root_factory = route.factory or self.root_factory + + root = root_factory(request) + attrs['root'] = root + + # find a context + traverser = adapters.queryAdapter(root, ITraverser) + if traverser is None: + traverser = ResourceTreeTraverser(root) + tdict = traverser(request) + + context, view_name, subpath, traversed, vroot, vroot_path = ( + tdict['context'], + tdict['view_name'], + tdict['subpath'], + tdict['traversed'], + tdict['virtual_root'], + tdict['virtual_root_path'] + ) + + attrs.update(tdict) + has_listeners and notify(ContextFound(request)) + + # find a view callable + context_iface = providedBy(context) + view_callable = adapters.lookup( + (IViewClassifier, request.request_iface, context_iface), + IView, name=view_name, default=None) + + # invoke the view callable + if view_callable is None: + if self.debug_notfound: + msg = ( + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, subpath, traversed, root, vroot, + vroot_path) + ) + logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) + else: + response = view_callable(context, request) has_listeners and notify(NewResponse(request, response)) if request.response_callbacks: request._process_response_callbacks(response) - return request, response, attrs.get('exc_info') + return request, response finally: - manager.pop() - if request is not None: - try: - if request.finished_callbacks: - request._process_finished_callbacks() - finally: - request.exc_info = None + if request is not None and request.finished_callbacks: + request._process_finished_callbacks() def __call__(self, environ, start_response): """ @@ -220,8 +183,49 @@ class Router(object): within the application registry; call ``start_response`` and return an iterable. """ + registry = self.registry request = self.request_factory(environ) - request.registry = self.registry - request, response = self.handle_request(request)[:2] - return response(request.environ, start_response) + threadlocals = {'registry':registry, 'request':request} + manager = self.threadlocal_manager + manager.push(threadlocals) + try: + request.registry = registry + request, response = self.handle_request(request) + return response(request.environ, start_response) + finally: + manager.pop() + +def exception_view_handler_factory(handler, registry): + has_listeners = registry.has_listeners + adapters = registry.adapters + notify = registry.notify + + def exception_view_handler(request): + attrs = request.__dict__ + try: + request, response = handler(request) + except Exception, exc: + # WARNING: do not assign the result of sys.exc_info() to a + # local var here, doing so will cause a leak + attrs['exc_info'] = sys.exc_info() + attrs['exception'] = exc + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] + request_iface = attrs['request_iface'] + provides = providedBy(exc) + for_ = (IExceptionViewClassifier, request_iface.combined, provides) + view_callable = adapters.lookup(for_, IView, default=None) + if view_callable is None: + raise + response = view_callable(exc, request) + has_listeners and notify(NewResponse(request, response)) + finally: + attrs['exc_info'] = None + attrs['exception'] = None + return request, response + + return exception_view_handler -- cgit v1.2.3 From 654a67df97623eb1890999584f0cc0299fa1944c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Aug 2011 08:48:20 -0400 Subject: add toposort and tests --- pyramid/tests/test_util.py | 66 +++++++++++++++++++++++++++++++++++++++ pyramid/util.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 247b61dad..8b6f47478 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -246,5 +246,71 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) +class Test_topological_sort(unittest.TestCase): + def _callFUT(self, items, partial_order, ignore_missing_partials=True): + from pyramid.util import topological_sort + return topological_sort(items, partial_order, ignore_missing_partials) + + def test_no_items_no_order(self): + result = self._callFUT([], []) + self.assertEqual(result, []) + + def test_no_order(self): + result = self._callFUT(['a', 'b'], []) + self.assertEqual(result, ['a', 'b']) + + def test_partial_order(self): + result = self._callFUT(['a', 'b', 'c'], [('b', 'c')]) + self.assertEqual(result, ['a', 'b', 'c']) + + def test_partial_order2(self): + result = self._callFUT(['a', 'b', 'c'], [('a', 'b'), ('b', 'c')]) + self.assertEqual(result, ['a', 'b', 'c']) + + def test_partial_order3(self): + result = self._callFUT(['a', 'b', 'c'], [('a', 'c'), ('b', 'a')]) + self.assertEqual(result, ['b', 'a', 'c']) + + def test_partial_order4(self): + result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c')]) + self.assertEqual(result, ['a', 'b', 'd', 'c']) + + def test_partial_order_missing_partial_a(self): + result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('f', 'c')]) + self.assertEqual(result, ['a', 'b', 'd', 'f', 'c']) + + def test_partial_order_missing_partial_b(self): + result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('c', 'f')]) + self.assertEqual(result, ['a', 'b', 'd', 'c', 'f']) + + def test_cycle_direct(self): + from pyramid.util import CyclicDependencyError + self.assertRaises( + CyclicDependencyError, + self._callFUT, + ['a', 'b', 'c', 'd'], [('c', 'd'), ('d', 'c')]) + + def test_cycle_indirect(self): + from pyramid.util import CyclicDependencyError + self.assertRaises( + CyclicDependencyError, + self._callFUT, + ['a', 'b', 'c', 'd', 'e'], + [('c', 'd'), ('d', 'e'), ('e', 'c')]) + +class TestCyclicDependencyError(unittest.TestCase): + def _makeOne(self, cycles): + from pyramid.util import CyclicDependencyError + return CyclicDependencyError(cycles) + + def test___str__(self): + exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) + result = str(exc) + self.assertEqual(result, + "'a' depends on ['c', 'd']; 'c' depends on ['a']") + +def reraise(exc): + raise exc + class Dummy(object): pass diff --git a/pyramid/util.py b/pyramid/util.py index 7fd1b0dc6..fa885b81e 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -206,3 +206,80 @@ class WeakOrderedSet(object): if self._order: oid = self._order[-1] return self._items[oid]() + +def topological_sort(items, partial_order, ignore_missing_partials=True): + """ + Stolen from http://www.bitinformation.com/art/python_topsort.html + (modified to sort initial roots in items order, and to ignore missing + partials). + + Given the example list of items ['item2', 'item3', 'item1', + 'item4'] and a 'partial order' list in the form [(item1, item2), + (item2, item3)], where the example tuples indicate that 'item1' + should precede 'item2' and 'item2' should precede 'item3', return + the sorted list of items ['item1', 'item2', 'item3', 'item4']. + Note that since 'item4' is not mentioned in the partial ordering + list, it will be at an arbitrary position in the returned list. + """ + def add_node(graph, node, roots): + if not graph.has_key(node): + roots.append(node) + graph[node] = [0] # 0 = number of arcs coming into this node + + def add_arc(graph, fromnode, tonode, roots): + graph[fromnode].append(tonode) + graph[tonode][0] = graph[tonode][0] + 1 + if tonode in roots: + roots.remove(tonode) + + graph = {} + roots = [] + + for v in items: + add_node(graph, v, roots) + + for a, b in partial_order: + if ignore_missing_partials: + # don't fail if a value is present in the partial_order + # list but missing in items. In this mode, we fake up a + # value instead of raising a KeyError when trying to use + # add_arc. The result will contain the faked item. + if not graph.has_key(a): + add_node(graph, a, roots) + elif not graph.has_key(b): + add_node(graph, b, roots) + add_arc(graph, a, b, roots) + + sorted = [] + + while roots: + root = roots.pop(0) + sorted.append(root) + for child in graph[root][1:]: + graph[child][0] = graph[child][0] - 1 + if graph[child][0] == 0: + roots.insert(0, child) + del graph[root] + + if graph: + # loop in input + cycledeps = {} + for k, v in graph.items(): + cycledeps[k] = v[1:] + raise CyclicDependencyError(cycledeps) + + return sorted + +class CyclicDependencyError(ConfigurationError): + def __init__(self, cycles): + self.cycles = cycles + + def __str__(self): + L = [] + cycles = self.cycles + for cycle in cycles: + dependent = cycle + dependees = cycles[cycle] + L.append('%r depends on %r' % (dependent, dependees)) + msg = '; '.join(L) + return msg -- cgit v1.2.3 From 2df6bee0cf734187b70f3362affc63d04809c581 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Aug 2011 08:50:25 -0400 Subject: remove unused code --- pyramid/tests/test_util.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 8b6f47478..2dd2dbd7f 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -309,8 +309,5 @@ class TestCyclicDependencyError(unittest.TestCase): self.assertEqual(result, "'a' depends on ['c', 'd']; 'c' depends on ['a']") -def reraise(exc): - raise exc - class Dummy(object): pass -- cgit v1.2.3 From e25fae8ebdc5b77ec1578186d11653430835f76e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Aug 2011 11:57:26 -0400 Subject: add docs, return only items in the list --- pyramid/config.py | 116 +++++++++++++++++++++++++++++++++++++++------ pyramid/tests/test_util.py | 4 +- pyramid/util.py | 2 +- 3 files changed, 105 insertions(+), 17 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 159422c22..e9f2dd2a5 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -898,17 +898,18 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_request_handler(self, handler_factory, name): + def add_request_handler(self, handler_factory, before=None, + after=None, name=None): """ Add a request handler factory. A request handler factory is used to - wrap the Pyramid router's primary request handling function. This is + wrap the Pyramid router's main request handling function. This is a feature that may be used by framework extensions, to provide, for example, view timing support and as a convenient place to hang bookkeeping code that examines exceptions before they are returned to the server. A request handler factory (passed as ``handler_factory``) must be a - callable (or a :term:`dotted Python name` to a callable) which + callable (or a :term:`dotted Python name` to such a callable) which accepts two arguments: ``handler`` and ``registry``. ``handler`` will be the request handler being wrapped. ``registry`` will be the Pyramid :term:`application registry` represented by this @@ -940,29 +941,116 @@ class Configurator(object): # if timing support is not enabled, return the original handler return handler - config.add_request_handler(timing_handler_factory, 'timing') + config.add_request_handler(timing_handler_factory) The ``request`` argument to the handler will be the request created by Pyramid's router when it receives a WSGI request. If more than one request handler factory is registered into a single - configuration, the request handlers will be chained together. The - first request handler factory added (in code execution order) will be - called with the default Pyramid request handler, the second handler - factory added will be called with the result of the first handler - factory, ad infinitum. The Pyramid router will use the outermost - wrapper in this chain (which is a bit like a WSGI middleware - "pipeline") as its handler function. - - The ``name`` argument to this function is required. The name is used + configuration, request handlers will be chained together. The first + request handler factory in the chain will be called with the default + Pyramid request handler, the second handler factory added will be + called with the result of the first handler factory, ad + infinitum. The Pyramid router will use the outermost wrapper in this + chain (which is a bit like a WSGI middleware "pipeline") as its + handler function. + + The deploying user can specify request handler inclusion and ordering + in his Pyramid configuration using the ``pyramid.handlers`` + configuration value. However, he is not required to do so. Instead, + he can rely on hinting information provided by you (the framework + extender) which provides some clues about where in his request + handler pipeline a request handler should wind up. This is achieved + by using the ``before`` and/or ``after`` arguments to + ``add_request_handler``. + + The ``before`` and ``after`` arguments provide hints to Pyramid about + which position in the handler chain this handler must assume when the + deploying user has not defined a ``pyramid.handlers`` configuration + value. ``before`` and ``after`` are relative to request *ingress* + order. Therefore, ``before`` means 'closer to + request ingress' and ``after`` means 'closer to Pyramid's main request + handler'. + + ``before`` or ``after`` may be either the name of another handler, + the name ``main`` representing Pyramid's main request handler, the + name ``ingress`` representing the request ingress point, or a sequence + of handler names (the sequence can itself include ``main`` or + ``ingress``). For example, if you'd like to hint that this handler + should always come before the + ``pyramid.router.exception_view_handler_factory`` handler but before + the ``pyramid_retry.retry_handler_factory`` handler: + + .. code-block:: python + :linenos: + + config.add_request_handler( + 'mypkg.handler_factory', + before='pyramid.router.exception_view_handler_factory', + after='pyramid_retry.retry_handler_factory') + + Or you might rather hint that a handler should always come before the + ``main`` handler but after retry and transaction management handlers: + + .. code-block:: python + :linenos: + + config.add_handler('mypkg.handler', before='main', + after=('pyramid_retry.retry_handler_factory', + 'pyramid_tm.tm_handler_factory') + + Or you might hint that a handler should come after a browser + id-generating handler, but you don't want to hint that it comes + before anything: + + .. code-block:: python + :linenos: + + config.add_handler( + 'mypkg.handler', + after='pyramid_browserid.browserid_handler_factory') + + The names provided to ``before`` and ``after`` don't need to + represent handlers that actually exist in the current deployment. + For example, if you say + ``before='pyramid_retry.retry_handler_factory'`` and the + ``pyramid_retry.retry_handler_factory`` handler is not configured + into the current deployment at all, no error will be raised when the + add_request_handler statement is executed; instead, the hint will be + effectively ignored. + + It is an error to specify an ``after`` value of ``main`` or a + ``before`` value of ``ingress``. + + Pyramid does its best to configure a sensible handlers pipeline when + the user does not provide a ``pyramid.handlers`` configuration + setting in his Pyramid configuration, even in the face of conflicting + hints, by using a topological sort on all handlers configured into + the system using ``before`` and ``after`` hinting as input to the + sort. However, ``before`` and ``after`` hinting are ignored + completely when the deploying user has specified an explicit + ``pyramid.handlers`` configuration value; this value specifies an + unambigous inclusion and ordering for handlers in a given deployment; + when it is provided, the topological sort that uses the ``before`` + and ``after`` hint information is never performed. + + A user can get both the effective and hinted handler ordering by + using the ``paster phandlers`` command. + + The ``name`` argument to this function is optional. The name is used as a key for conflict detection. No two request handler factories may share the same name in the same configuration (unless :ref:`automatic_conflict_resolution` is able to resolve the conflict - or this is an autocommitting configurator). + or this is an autocommitting configurator). By default, a name will + be the :term:`Python dotted name` of the object passed as + ``handler_factory``. .. note:: This feature is new as of Pyramid 1.1.1. """ handler_factory = self.maybe_dotted(handler_factory) + if name is None: + name = '.'.join( + (handler_factory.__module__, handler_factory.__name__)) def register(): registry = self.registry registry.registerUtility(handler_factory, IRequestHandlerFactory, diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 2dd2dbd7f..62477ed9f 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -277,11 +277,11 @@ class Test_topological_sort(unittest.TestCase): def test_partial_order_missing_partial_a(self): result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('f', 'c')]) - self.assertEqual(result, ['a', 'b', 'd', 'f', 'c']) + self.assertEqual(result, ['a', 'b', 'd', 'c']) def test_partial_order_missing_partial_b(self): result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('c', 'f')]) - self.assertEqual(result, ['a', 'b', 'd', 'c', 'f']) + self.assertEqual(result, ['a', 'b', 'd', 'c']) def test_cycle_direct(self): from pyramid.util import CyclicDependencyError diff --git a/pyramid/util.py b/pyramid/util.py index fa885b81e..5de8aa37a 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -268,7 +268,7 @@ def topological_sort(items, partial_order, ignore_missing_partials=True): cycledeps[k] = v[1:] raise CyclicDependencyError(cycledeps) - return sorted + return [ x for x in sorted if x in items ] class CyclicDependencyError(ConfigurationError): def __init__(self, cycles): -- cgit v1.2.3 From fc406adb99573cc99ef388693c909d5d2de16e19 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Aug 2011 11:57:41 -0400 Subject: - Deprecate pyramid.security.view_execution_permitted (it only works for traversal). --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index fda59d7b4..37c79035b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,9 @@ Pyramid TODOs Should-Have ----------- +- Deprecate pyramid.security.view_execution_permitted (it only works for + traversal). + - Merge https://github.com/Pylons/pyramid/pull/242 (IPython update; requires test fixes and additional test coverage). -- cgit v1.2.3 From 5083c3bb7445055944bd3a607d5f2f8b61e1c663 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Fri, 5 Aug 2011 20:31:30 -0500 Subject: Add small explanation about omitting request parameter in render_to_response as requested on issue 248 --- pyramid/renderers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index c718cf1bb..a06067c97 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -98,7 +98,9 @@ def render_to_response(renderer_name, value, request=None, package=None): Supply a ``request`` parameter in order to provide the renderer with the most correct 'system' values (``request`` and ``context`` - in particular). + in particular). Keep in mind that if the ``request`` parameter is + not passed in, any changes to ``request.response`` attributes made + before calling this function will be ignored. """ try: -- cgit v1.2.3 From 0d49632e7f9fd60c2f02f09a34b922b567186d4d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 00:34:18 -0400 Subject: some tests fail; simpler registration of handlers --- pyramid/config.py | 220 ++++++++++++++++++++----------------------- pyramid/interfaces.py | 15 +-- pyramid/router.py | 21 ++--- pyramid/tests/test_config.py | 86 ++++++++--------- pyramid/tests/test_router.py | 94 +++++++++--------- 5 files changed, 203 insertions(+), 233 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index e9f2dd2a5..5288d1d48 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -38,8 +38,7 @@ from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IRequestFactory -from pyramid.interfaces import IRequestHandlerFactory -from pyramid.interfaces import IRequestHandlerFactories +from pyramid.interfaces import IRequestHandlerManager from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -720,8 +719,12 @@ class Configurator(object): if settings: includes = settings.pop('pyramid.include', '') includes = [x.strip() for x in includes.splitlines()] + expl_handler_factories = settings.pop('pyramid.request_handlers','') + expl_handler_factories = [x.strip() for x in + expl_handler_factories.splitlines()] else: includes = [] + expl_handler_factories = [] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -731,6 +734,11 @@ class Configurator(object): # cope with WebOb exc objects not decoratored with IExceptionResponse from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) + # add a handler manager + handler_manager = RequestHandlerManager() + registry.registerUtility(handler_manager, IRequestHandlerManager) + self._add_request_handler('pyramid.router.exc_view_handler_factory', + explicit=False) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) @@ -777,6 +785,8 @@ class Configurator(object): if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) self.commit() + for factory in expl_handler_factories: + self._add_request_handler(factory, explicit=True) for inc in includes: self.include(inc) @@ -898,15 +908,17 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_request_handler(self, handler_factory, before=None, - after=None, name=None): + def add_request_handler(self, handler_factory): """ Add a request handler factory. A request handler factory is used to - wrap the Pyramid router's main request handling function. This is - a feature that may be used by framework extensions, to provide, for + wrap the Pyramid router's main request handling function. This is a + feature that may be used by framework extensions, to provide, for example, view timing support and as a convenient place to hang bookkeeping code that examines exceptions before they are returned to - the server. + the WSGI server. Request handlers behave a bit like :mod:`WSGI` + 'middleware' but they have the benefit of running in a context in + which they have access tot he Pyramid :term:`application registry` as + well as the Pyramid rendering machinery. A request handler factory (passed as ``handler_factory``) must be a callable (or a :term:`dotted Python name` to such a callable) which @@ -919,15 +931,19 @@ class Configurator(object): A request handler accepts a :term:`request` object and returns a :term:`response` object. - Here's an example of creating both a handler factory and a handler, - and registering the handler factory: + Here's an example creating a handler factory and registering the + handler factory: .. code-block:: python import time + from pyramid.settings import asbool + import logging + + log = logging.getLogger(__name__) def timing_handler_factory(handler, registry): - if registry.settings['do_timing']: + if asbool(registry.settings.get('do_timing')): # if timing support is enabled, return a wrapper def timing_handler(request): start = time.time() @@ -935,10 +951,12 @@ class Configurator(object): response = handler(request) finally: end = time.time() - print: 'The request took %s seconds' % (end - start) + log.debug('The request took %s seconds' % + (end - start)) return response return timing_handler - # if timing support is not enabled, return the original handler + # if timing support is not enabled, return the original + # handler return handler config.add_request_handler(timing_handler_factory) @@ -946,120 +964,61 @@ class Configurator(object): The ``request`` argument to the handler will be the request created by Pyramid's router when it receives a WSGI request. - If more than one request handler factory is registered into a single - configuration, request handlers will be chained together. The first - request handler factory in the chain will be called with the default - Pyramid request handler, the second handler factory added will be - called with the result of the first handler factory, ad - infinitum. The Pyramid router will use the outermost wrapper in this - chain (which is a bit like a WSGI middleware "pipeline") as its - handler function. - - The deploying user can specify request handler inclusion and ordering - in his Pyramid configuration using the ``pyramid.handlers`` - configuration value. However, he is not required to do so. Instead, - he can rely on hinting information provided by you (the framework - extender) which provides some clues about where in his request - handler pipeline a request handler should wind up. This is achieved - by using the ``before`` and/or ``after`` arguments to - ``add_request_handler``. - - The ``before`` and ``after`` arguments provide hints to Pyramid about - which position in the handler chain this handler must assume when the - deploying user has not defined a ``pyramid.handlers`` configuration - value. ``before`` and ``after`` are relative to request *ingress* - order. Therefore, ``before`` means 'closer to - request ingress' and ``after`` means 'closer to Pyramid's main request - handler'. - - ``before`` or ``after`` may be either the name of another handler, - the name ``main`` representing Pyramid's main request handler, the - name ``ingress`` representing the request ingress point, or a sequence - of handler names (the sequence can itself include ``main`` or - ``ingress``). For example, if you'd like to hint that this handler - should always come before the - ``pyramid.router.exception_view_handler_factory`` handler but before - the ``pyramid_retry.retry_handler_factory`` handler: - - .. code-block:: python - :linenos: - - config.add_request_handler( - 'mypkg.handler_factory', - before='pyramid.router.exception_view_handler_factory', - after='pyramid_retry.retry_handler_factory') - - Or you might rather hint that a handler should always come before the - ``main`` handler but after retry and transaction management handlers: - - .. code-block:: python - :linenos: - - config.add_handler('mypkg.handler', before='main', - after=('pyramid_retry.retry_handler_factory', - 'pyramid_tm.tm_handler_factory') - - Or you might hint that a handler should come after a browser - id-generating handler, but you don't want to hint that it comes - before anything: - - .. code-block:: python - :linenos: - - config.add_handler( - 'mypkg.handler', - after='pyramid_browserid.browserid_handler_factory') - - The names provided to ``before`` and ``after`` don't need to - represent handlers that actually exist in the current deployment. - For example, if you say - ``before='pyramid_retry.retry_handler_factory'`` and the - ``pyramid_retry.retry_handler_factory`` handler is not configured - into the current deployment at all, no error will be raised when the - add_request_handler statement is executed; instead, the hint will be - effectively ignored. - - It is an error to specify an ``after`` value of ``main`` or a - ``before`` value of ``ingress``. - - Pyramid does its best to configure a sensible handlers pipeline when - the user does not provide a ``pyramid.handlers`` configuration - setting in his Pyramid configuration, even in the face of conflicting - hints, by using a topological sort on all handlers configured into - the system using ``before`` and ``after`` hinting as input to the - sort. However, ``before`` and ``after`` hinting are ignored - completely when the deploying user has specified an explicit - ``pyramid.handlers`` configuration value; this value specifies an - unambigous inclusion and ordering for handlers in a given deployment; - when it is provided, the topological sort that uses the ``before`` - and ``after`` hint information is never performed. - - A user can get both the effective and hinted handler ordering by - using the ``paster phandlers`` command. - - The ``name`` argument to this function is optional. The name is used - as a key for conflict detection. No two request handler factories - may share the same name in the same configuration (unless - :ref:`automatic_conflict_resolution` is able to resolve the conflict - or this is an autocommitting configurator). By default, a name will - be the :term:`Python dotted name` of the object passed as - ``handler_factory``. + If more than one call to ``add_request_handler`` is made within a + single application configuration, request handlers will be chained + together. The first request handler factory added will be called + with the default Pyramid request handler as its ``handler`` argument, + the second handler factory added will be called with the result of + the first handler factory, and so on, ad infinitum. The Pyramid + router will use the outermost handler produced by this chain (the + very last handler added) as its handler function. + + By default, the ordering of the chain is controlled entirely by the + relative ordering of calls to ``add_request_handler``. However, the + deploying user can override request handler inclusion and ordering in + his Pyramid configuration using the ``pyramid.request_handlers`` + settings value. When used, this settings value should be a list of + Python dotted names which imply the ordering (and inclusion) of + handler factories in the request handler chain. + + Pyramid will prevent the same request handler factory from being + added to the request handling chain more than once using + configuration conflict detection. If you wish to add the same + handler factory more than once in a configuration, you should either: + a) use a handler that is an *instance* rather than a function or + class, or b) use a function or class with the same logic as the other + it conflicts with but with a different ``__name__`` attribute. + + A user can get a representation of both the implicit request handler + ordering (the ordering specified by calls to ``add_request_handler``) + and the explicit request handler ordering (specified by the + ``pyramid.request_handlers`` setting) orderings using the ``paster + phandlers`` command. Handler factories which are functions or + classes will show up as a standard Python dotted name in the + ``phandlers`` output. Handler factories which are *instances* will + show their module and class name; the Python object id of the + instance will be appended. .. note:: This feature is new as of Pyramid 1.1.1. """ + return self._add_request_handler(handler_factory, explicit=False) + + def _add_request_handler(self, handler_factory, explicit): handler_factory = self.maybe_dotted(handler_factory) - if name is None: - name = '.'.join( - (handler_factory.__module__, handler_factory.__name__)) + if hasattr(handler_factory, '__name__'): + # function or class + name = '.'.join((handler_factory.__module__, + handler_factory.__name__)) + else: + # instance + name = '.'.join(handler_factory.__module__, + handler_factory.__class__.__name__, + str(id(handler_factory))) def register(): registry = self.registry - registry.registerUtility(handler_factory, IRequestHandlerFactory, - name=name) - existing_names = registry.queryUtility(IRequestHandlerFactories, - default=[]) - existing_names.append(name) - registry.registerUtility(existing_names, IRequestHandlerFactories) - self.action(('requesthandler', name), register) + handler_manager = registry.getUtility(IRequestHandlerManager) + handler_manager.add(name, handler_factory, explicit) + self.action(('request_handler', name), register) @action_method def add_subscriber(self, subscriber, iface=None): @@ -3495,3 +3454,24 @@ def isexception(o): ) global_registries = WeakOrderedSet() + +class RequestHandlerManager(object): + implements(IRequestHandlerManager) + def __init__(self): + self.explicit = [] + self.implicit = [] + + def add(self, name, factory, explicit=False): + if explicit: + self.explicit.append((name, factory)) + else: + self.implicit.append((name, factory)) + + def __call__(self, handler, registry): + handler_factories = self.implicit + if self.explicit: + handler_factories = self.explicit + for name, factory in handler_factories[::-1]: + handler = factory(handler, registry) + return handler + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 41e896adf..0c4f4bc76 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -446,23 +446,16 @@ class IMultiDict(Interface): # docs-only interface class IRequest(Interface): """ Request type interface attached to all request objects """ -class IRequestHandlerFactories(Interface): +class IRequestHandlerManager(Interface): """ Marker interface for utility registration representing the ordered set of a configuration's request handler factories""" -class IRequestHandlerFactory(Interface): - """ A request handler factory can be used to augment Pyramid's default - mainloop request handling.""" - def __call__(self, handler, registry): - """ Return an IRequestHandler; the ``handler`` argument passed will - be the previous request handler added, or the default request handler - if no request handlers have yet been added .""" - class IRequestHandler(Interface): """ """ def __call__(self, request): - """ Must return an IResponse or raise an exception. The ``request`` - argument will be an instance of an object that provides IRequest.""" + """ Must return a tuple of IReqest, IResponse or raise an exception. + The ``request`` argument will be an instance of an object that + provides IRequest.""" IRequest.combined = IRequest # for exception view lookups diff --git a/pyramid/router.py b/pyramid/router.py index 5cc144996..b068458be 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -14,8 +14,7 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IRequestHandlerFactory -from pyramid.interfaces import IRequestHandlerFactories +from pyramid.interfaces import IRequestHandlerManager from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -40,17 +39,13 @@ class Router(object): self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) - handler_factory_names = q(IRequestHandlerFactories) - handler = self.handle_request - if handler_factory_names: - for name in handler_factory_names: - handler_factory = registry.getUtility(IRequestHandlerFactory, - name=name) - handler = handler_factory(handler, registry) - self.handle_request = handler + handler_manager = q(IRequestHandlerManager) + if handler_manager is None: + self.handle_request = exc_view_handler_factory(self.handle_request, + registry) else: - self.handle_request = exception_view_handler_factory( - self.handle_request, registry) + self.handle_request = handler_manager(self.handle_request, registry) + self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings @@ -195,7 +190,7 @@ class Router(object): finally: manager.pop() -def exception_view_handler_factory(handler, registry): +def exc_view_handler_factory(handler, registry): has_listeners = registry.has_listeners adapters = registry.adapters notify = registry.notify diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 250c53b9a..fbdf9e0b5 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -612,48 +612,48 @@ pyramid.tests.test_config.dummy_include2""", settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) - def test_add_request_handlers_names_distinct(self): - from pyramid.interfaces import IRequestHandlerFactories - from pyramid.interfaces import IRequestHandlerFactory - def factory1(handler, registry): return handler - def factory2(handler, registry): return handler - config = self._makeOne() - config.add_request_handler(factory1, 'name1') - config.add_request_handler(factory2, 'name2') - config.commit() - names = config.registry.queryUtility(IRequestHandlerFactories) - self.assertEqual(names, ['name1', 'name2']) - f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - f2 = config.registry.getUtility(IRequestHandlerFactory, name='name2') - self.assertEqual(f1, factory1) - self.assertEqual(f2, factory2) - - def test_add_request_handlers_dottednames(self): - import pyramid.tests - from pyramid.interfaces import IRequestHandlerFactories - from pyramid.interfaces import IRequestHandlerFactory - config = self._makeOne() - config.add_request_handler('pyramid.tests', 'name1') - config.commit() - names = config.registry.queryUtility(IRequestHandlerFactories) - self.assertEqual(names, ['name1']) - f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - self.assertEqual(f1, pyramid.tests) - - def test_add_request_handlers_names_overlap(self): - from pyramid.interfaces import IRequestHandlerFactories - from pyramid.interfaces import IRequestHandlerFactory - def factory1(handler, registry): return handler - def factory2(handler, registry): return handler - def factory3(handler, registry): return handler - config = self._makeOne(autocommit=True) - config.add_request_handler(factory1, 'name1') - config.add_request_handler(factory2, 'name2') - config.add_request_handler(factory3, 'name1') - names = config.registry.queryUtility(IRequestHandlerFactories) - self.assertEqual(names, ['name1', 'name2', 'name1']) - f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - self.assertEqual(f3, factory3) + ## def test_add_request_handlers_names_distinct(self): + ## from pyramid.interfaces import IRequestHandlerFactories + ## from pyramid.interfaces import IRequestHandlerFactory + ## def factory1(handler, registry): return handler + ## def factory2(handler, registry): return handler + ## config = self._makeOne() + ## config.add_request_handler(factory1, 'name1') + ## config.add_request_handler(factory2, 'name2') + ## config.commit() + ## names = config.registry.queryUtility(IRequestHandlerFactories) + ## self.assertEqual(names, ['name1', 'name2']) + ## f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + ## f2 = config.registry.getUtility(IRequestHandlerFactory, name='name2') + ## self.assertEqual(f1, factory1) + ## self.assertEqual(f2, factory2) + + ## def test_add_request_handlers_dottednames(self): + ## import pyramid.tests + ## from pyramid.interfaces import IRequestHandlerFactories + ## from pyramid.interfaces import IRequestHandlerFactory + ## config = self._makeOne() + ## config.add_request_handler('pyramid.tests', 'name1') + ## config.commit() + ## names = config.registry.queryUtility(IRequestHandlerFactories) + ## self.assertEqual(names, ['name1']) + ## f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + ## self.assertEqual(f1, pyramid.tests) + + ## def test_add_request_handlers_names_overlap(self): + ## from pyramid.interfaces import IRequestHandlerFactories + ## from pyramid.interfaces import IRequestHandlerFactory + ## def factory1(handler, registry): return handler + ## def factory2(handler, registry): return handler + ## def factory3(handler, registry): return handler + ## config = self._makeOne(autocommit=True) + ## config.add_request_handler(factory1, 'name1') + ## config.add_request_handler(factory2, 'name2') + ## config.add_request_handler(factory3, 'name1') + ## names = config.registry.queryUtility(IRequestHandlerFactories) + ## self.assertEqual(names, ['name1', 'name2', 'name1']) + ## f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1') + ## self.assertEqual(f3, factory3) def test_add_subscriber_defaults(self): from zope.interface import implements @@ -5645,6 +5645,8 @@ class DummyRegistry(object): self.adapters.append((arg, kw)) def queryAdapter(self, *arg, **kw): return self.adaptation + def getUtility(self, *arg, **kw): + return self.utilities[-1] def parse_httpdate(s): import datetime diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index b943f1ee6..633fe63f5 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -134,53 +134,53 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.request_factory, DummyRequestFactory) - def test_request_handler_factories(self): - from pyramid.interfaces import IRequestHandlerFactory - from pyramid.interfaces import IRequestHandlerFactories - L = [] - def handler_factory1(handler, registry): - L.append((handler, registry)) - def wrapper(request): - request.environ['handled'].append('one') - return handler(request) - wrapper.name = 'one' - wrapper.child = handler - return wrapper - def handler_factory2(handler, registry): - L.append((handler, registry)) - def wrapper(request): - request.environ['handled'] = ['two'] - return handler(request) - wrapper.name = 'two' - wrapper.child = handler - return wrapper - self.registry.registerUtility(['one', 'two'], IRequestHandlerFactories) - self.registry.registerUtility(handler_factory1, - IRequestHandlerFactory, name='one') - self.registry.registerUtility(handler_factory2, - IRequestHandlerFactory, name='two') - router = self._makeOne() - self.assertEqual(router.handle_request.name, 'two') - self.assertEqual(router.handle_request.child.name, 'one') - self.assertEqual(router.handle_request.child.child.__name__, - 'handle_request') - from pyramid.response import Response - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import IResponse - context = DummyContext() - self._registerTraverserFactory(context) - environ = self._makeEnviron() - view = DummyView('abc') - self._registerView(self.config.derive_view(view), '', - IViewClassifier, None, None) - start_response = DummyStartResponse() - def make_response(s): - return Response(s) - router.registry.registerAdapter(make_response, (str,), IResponse) - app_iter = router(environ, start_response) - self.assertEqual(app_iter, ['abc']) - self.assertEqual(start_response.status, '200 OK') - self.assertEqual(environ['handled'], ['two', 'one']) + ## def test_request_handler_factories(self): + ## from pyramid.interfaces import IRequestHandlerFactory + ## from pyramid.interfaces import IRequestHandlerFactories + ## L = [] + ## def handler_factory1(handler, registry): + ## L.append((handler, registry)) + ## def wrapper(request): + ## request.environ['handled'].append('one') + ## return handler(request) + ## wrapper.name = 'one' + ## wrapper.child = handler + ## return wrapper + ## def handler_factory2(handler, registry): + ## L.append((handler, registry)) + ## def wrapper(request): + ## request.environ['handled'] = ['two'] + ## return handler(request) + ## wrapper.name = 'two' + ## wrapper.child = handler + ## return wrapper + ## self.registry.registerUtility(['one', 'two'], IRequestHandlerFactories) + ## self.registry.registerUtility(handler_factory1, + ## IRequestHandlerFactory, name='one') + ## self.registry.registerUtility(handler_factory2, + ## IRequestHandlerFactory, name='two') + ## router = self._makeOne() + ## self.assertEqual(router.handle_request.name, 'two') + ## self.assertEqual(router.handle_request.child.name, 'one') + ## self.assertEqual(router.handle_request.child.child.__name__, + ## 'handle_request') + ## from pyramid.response import Response + ## from pyramid.interfaces import IViewClassifier + ## from pyramid.interfaces import IResponse + ## context = DummyContext() + ## self._registerTraverserFactory(context) + ## environ = self._makeEnviron() + ## view = DummyView('abc') + ## self._registerView(self.config.derive_view(view), '', + ## IViewClassifier, None, None) + ## start_response = DummyStartResponse() + ## def make_response(s): + ## return Response(s) + ## router.registry.registerAdapter(make_response, (str,), IResponse) + ## app_iter = router(environ, start_response) + ## self.assertEqual(app_iter, ['abc']) + ## self.assertEqual(start_response.status, '200 OK') + ## self.assertEqual(environ['handled'], ['two', 'one']) def test_call_traverser_default(self): from pyramid.httpexceptions import HTTPNotFound -- cgit v1.2.3 From 28b40471d5a3558ee0dfe1d445c5b8bec0c1cf0b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 16:31:22 -0400 Subject: make tests pass; remove toposort stuff --- pyramid/config.py | 14 +++---- pyramid/interfaces.py | 2 +- pyramid/router.py | 6 +-- pyramid/tests/test_config.py | 4 +- pyramid/tests/test_router.py | 93 ++++++++++++++++++++++---------------------- pyramid/tests/test_util.py | 63 ------------------------------ pyramid/util.py | 76 ------------------------------------ 7 files changed, 59 insertions(+), 199 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 5288d1d48..914bfbd53 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -38,7 +38,7 @@ from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IRequestFactory -from pyramid.interfaces import IRequestHandlerManager +from pyramid.interfaces import IRequestHandlers from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -735,8 +735,8 @@ class Configurator(object): from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) # add a handler manager - handler_manager = RequestHandlerManager() - registry.registerUtility(handler_manager, IRequestHandlerManager) + handler_manager = RequestHandlers() + registry.registerUtility(handler_manager, IRequestHandlers) self._add_request_handler('pyramid.router.exc_view_handler_factory', explicit=False) @@ -1016,7 +1016,7 @@ class Configurator(object): str(id(handler_factory))) def register(): registry = self.registry - handler_manager = registry.getUtility(IRequestHandlerManager) + handler_manager = registry.getUtility(IRequestHandlers) handler_manager.add(name, handler_factory, explicit) self.action(('request_handler', name), register) @@ -3455,8 +3455,8 @@ def isexception(o): global_registries = WeakOrderedSet() -class RequestHandlerManager(object): - implements(IRequestHandlerManager) +class RequestHandlers(object): + implements(IRequestHandlers) def __init__(self): self.explicit = [] self.implicit = [] @@ -3471,7 +3471,7 @@ class RequestHandlerManager(object): handler_factories = self.implicit if self.explicit: handler_factories = self.explicit - for name, factory in handler_factories[::-1]: + for name, factory in handler_factories: handler = factory(handler, registry) return handler diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 0c4f4bc76..c4bacd46d 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -446,7 +446,7 @@ class IMultiDict(Interface): # docs-only interface class IRequest(Interface): """ Request type interface attached to all request objects """ -class IRequestHandlerManager(Interface): +class IRequestHandlers(Interface): """ Marker interface for utility registration representing the ordered set of a configuration's request handler factories""" diff --git a/pyramid/router.py b/pyramid/router.py index b068458be..d8f5a08da 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -14,7 +14,7 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IRequestHandlerManager +from pyramid.interfaces import IRequestHandlers from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -39,7 +39,7 @@ class Router(object): self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) - handler_manager = q(IRequestHandlerManager) + handler_manager = q(IRequestHandlers) if handler_manager is None: self.handle_request = exc_view_handler_factory(self.handle_request, registry) @@ -219,7 +219,7 @@ def exc_view_handler_factory(handler, registry): has_listeners and notify(NewResponse(request, response)) finally: attrs['exc_info'] = None - attrs['exception'] = None + return request, response return exception_view_handler diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index fbdf9e0b5..ebc3f85cc 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -336,6 +336,7 @@ class ConfiguratorTests(unittest.TestCase): reg = DummyRegistry() config = self._makeOne(reg) config.add_view = lambda *arg, **kw: False + config._add_request_handler = lambda *arg, **kw: False config.setup_registry() self.assertEqual(reg.has_listeners, True) @@ -347,6 +348,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(reg) views = [] config.add_view = lambda *arg, **kw: views.append((arg, kw)) + config._add_request_handler = lambda *arg, **kw: False config.setup_registry() self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) @@ -5645,8 +5647,6 @@ class DummyRegistry(object): self.adapters.append((arg, kw)) def queryAdapter(self, *arg, **kw): return self.adaptation - def getUtility(self, *arg, **kw): - return self.utilities[-1] def parse_httpdate(s): import datetime diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 633fe63f5..6921de179 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -134,53 +134,52 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.request_factory, DummyRequestFactory) - ## def test_request_handler_factories(self): - ## from pyramid.interfaces import IRequestHandlerFactory - ## from pyramid.interfaces import IRequestHandlerFactories - ## L = [] - ## def handler_factory1(handler, registry): - ## L.append((handler, registry)) - ## def wrapper(request): - ## request.environ['handled'].append('one') - ## return handler(request) - ## wrapper.name = 'one' - ## wrapper.child = handler - ## return wrapper - ## def handler_factory2(handler, registry): - ## L.append((handler, registry)) - ## def wrapper(request): - ## request.environ['handled'] = ['two'] - ## return handler(request) - ## wrapper.name = 'two' - ## wrapper.child = handler - ## return wrapper - ## self.registry.registerUtility(['one', 'two'], IRequestHandlerFactories) - ## self.registry.registerUtility(handler_factory1, - ## IRequestHandlerFactory, name='one') - ## self.registry.registerUtility(handler_factory2, - ## IRequestHandlerFactory, name='two') - ## router = self._makeOne() - ## self.assertEqual(router.handle_request.name, 'two') - ## self.assertEqual(router.handle_request.child.name, 'one') - ## self.assertEqual(router.handle_request.child.child.__name__, - ## 'handle_request') - ## from pyramid.response import Response - ## from pyramid.interfaces import IViewClassifier - ## from pyramid.interfaces import IResponse - ## context = DummyContext() - ## self._registerTraverserFactory(context) - ## environ = self._makeEnviron() - ## view = DummyView('abc') - ## self._registerView(self.config.derive_view(view), '', - ## IViewClassifier, None, None) - ## start_response = DummyStartResponse() - ## def make_response(s): - ## return Response(s) - ## router.registry.registerAdapter(make_response, (str,), IResponse) - ## app_iter = router(environ, start_response) - ## self.assertEqual(app_iter, ['abc']) - ## self.assertEqual(start_response.status, '200 OK') - ## self.assertEqual(environ['handled'], ['two', 'one']) + def test_request_handler_factories(self): + from pyramid.interfaces import IRequestHandlers + from pyramid.config import RequestHandlers + handler_manager = RequestHandlers() + self.registry.registerUtility(handler_manager, IRequestHandlers) + L = [] + def handler_factory1(handler, registry): + L.append((handler, registry)) + def wrapper(request): + request.environ['handled'].append('one') + return handler(request) + wrapper.name = 'one' + wrapper.child = handler + return wrapper + def handler_factory2(handler, registry): + L.append((handler, registry)) + def wrapper(request): + request.environ['handled'] = ['two'] + return handler(request) + wrapper.name = 'two' + wrapper.child = handler + return wrapper + handler_manager.add('one', handler_factory1) + handler_manager.add('two', handler_factory2) + router = self._makeOne() + self.assertEqual(router.handle_request.name, 'two') + self.assertEqual(router.handle_request.child.name, 'one') + self.assertEqual(router.handle_request.child.child.__name__, + 'handle_request') + from pyramid.response import Response + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IResponse + context = DummyContext() + self._registerTraverserFactory(context) + environ = self._makeEnviron() + view = DummyView('abc') + self._registerView(self.config.derive_view(view), '', + IViewClassifier, None, None) + start_response = DummyStartResponse() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + app_iter = router(environ, start_response) + self.assertEqual(app_iter, ['abc']) + self.assertEqual(start_response.status, '200 OK') + self.assertEqual(environ['handled'], ['two', 'one']) def test_call_traverser_default(self): from pyramid.httpexceptions import HTTPNotFound diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 62477ed9f..247b61dad 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -246,68 +246,5 @@ class Test_WeakOrderedSet(unittest.TestCase): self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) -class Test_topological_sort(unittest.TestCase): - def _callFUT(self, items, partial_order, ignore_missing_partials=True): - from pyramid.util import topological_sort - return topological_sort(items, partial_order, ignore_missing_partials) - - def test_no_items_no_order(self): - result = self._callFUT([], []) - self.assertEqual(result, []) - - def test_no_order(self): - result = self._callFUT(['a', 'b'], []) - self.assertEqual(result, ['a', 'b']) - - def test_partial_order(self): - result = self._callFUT(['a', 'b', 'c'], [('b', 'c')]) - self.assertEqual(result, ['a', 'b', 'c']) - - def test_partial_order2(self): - result = self._callFUT(['a', 'b', 'c'], [('a', 'b'), ('b', 'c')]) - self.assertEqual(result, ['a', 'b', 'c']) - - def test_partial_order3(self): - result = self._callFUT(['a', 'b', 'c'], [('a', 'c'), ('b', 'a')]) - self.assertEqual(result, ['b', 'a', 'c']) - - def test_partial_order4(self): - result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c')]) - self.assertEqual(result, ['a', 'b', 'd', 'c']) - - def test_partial_order_missing_partial_a(self): - result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('f', 'c')]) - self.assertEqual(result, ['a', 'b', 'd', 'c']) - - def test_partial_order_missing_partial_b(self): - result = self._callFUT(['a', 'b', 'c', 'd'], [('d', 'c'), ('c', 'f')]) - self.assertEqual(result, ['a', 'b', 'd', 'c']) - - def test_cycle_direct(self): - from pyramid.util import CyclicDependencyError - self.assertRaises( - CyclicDependencyError, - self._callFUT, - ['a', 'b', 'c', 'd'], [('c', 'd'), ('d', 'c')]) - - def test_cycle_indirect(self): - from pyramid.util import CyclicDependencyError - self.assertRaises( - CyclicDependencyError, - self._callFUT, - ['a', 'b', 'c', 'd', 'e'], - [('c', 'd'), ('d', 'e'), ('e', 'c')]) - -class TestCyclicDependencyError(unittest.TestCase): - def _makeOne(self, cycles): - from pyramid.util import CyclicDependencyError - return CyclicDependencyError(cycles) - - def test___str__(self): - exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) - result = str(exc) - self.assertEqual(result, - "'a' depends on ['c', 'd']; 'c' depends on ['a']") - class Dummy(object): pass diff --git a/pyramid/util.py b/pyramid/util.py index 5de8aa37a..c0e7640c4 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -207,79 +207,3 @@ class WeakOrderedSet(object): oid = self._order[-1] return self._items[oid]() -def topological_sort(items, partial_order, ignore_missing_partials=True): - """ - Stolen from http://www.bitinformation.com/art/python_topsort.html - (modified to sort initial roots in items order, and to ignore missing - partials). - - Given the example list of items ['item2', 'item3', 'item1', - 'item4'] and a 'partial order' list in the form [(item1, item2), - (item2, item3)], where the example tuples indicate that 'item1' - should precede 'item2' and 'item2' should precede 'item3', return - the sorted list of items ['item1', 'item2', 'item3', 'item4']. - Note that since 'item4' is not mentioned in the partial ordering - list, it will be at an arbitrary position in the returned list. - """ - def add_node(graph, node, roots): - if not graph.has_key(node): - roots.append(node) - graph[node] = [0] # 0 = number of arcs coming into this node - - def add_arc(graph, fromnode, tonode, roots): - graph[fromnode].append(tonode) - graph[tonode][0] = graph[tonode][0] + 1 - if tonode in roots: - roots.remove(tonode) - - graph = {} - roots = [] - - for v in items: - add_node(graph, v, roots) - - for a, b in partial_order: - if ignore_missing_partials: - # don't fail if a value is present in the partial_order - # list but missing in items. In this mode, we fake up a - # value instead of raising a KeyError when trying to use - # add_arc. The result will contain the faked item. - if not graph.has_key(a): - add_node(graph, a, roots) - elif not graph.has_key(b): - add_node(graph, b, roots) - add_arc(graph, a, b, roots) - - sorted = [] - - while roots: - root = roots.pop(0) - sorted.append(root) - for child in graph[root][1:]: - graph[child][0] = graph[child][0] - 1 - if graph[child][0] == 0: - roots.insert(0, child) - del graph[root] - - if graph: - # loop in input - cycledeps = {} - for k, v in graph.items(): - cycledeps[k] = v[1:] - raise CyclicDependencyError(cycledeps) - - return [ x for x in sorted if x in items ] - -class CyclicDependencyError(ConfigurationError): - def __init__(self, cycles): - self.cycles = cycles - - def __str__(self): - L = [] - cycles = self.cycles - for cycle in cycles: - dependent = cycle - dependees = cycles[cycle] - L.append('%r depends on %r' % (dependent, dependees)) - msg = '; '.join(L) - return msg -- cgit v1.2.3 From 4f321df3ae4f09445555b653ef421f6b6fd97069 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 16:35:26 -0400 Subject: dont pop pyramid.includes or pyramid.request_handlers from the settings --- pyramid/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 914bfbd53..6a7b4d328 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -717,9 +717,9 @@ class Configurator(object): other settings using the configurator's current registry, as per the descriptions in the Configurator constructor.""" if settings: - includes = settings.pop('pyramid.include', '') + includes = settings.get('pyramid.include', '') includes = [x.strip() for x in includes.splitlines()] - expl_handler_factories = settings.pop('pyramid.request_handlers','') + expl_handler_factories = settings.get('pyramid.request_handlers','') expl_handler_factories = [x.strip() for x in expl_handler_factories.splitlines()] else: -- cgit v1.2.3 From b4843bc087524320460fb3fca9d33688cafa0dbb Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 17:38:37 -0400 Subject: requesthandlers->tweens; add ptweens command --- pyramid/config.py | 183 +++++++++++++++++++++++-------------------- pyramid/interfaces.py | 4 +- pyramid/paster.py | 66 +++++++++++++++- pyramid/router.py | 14 ++-- pyramid/tests/test_config.py | 4 +- pyramid/tests/test_router.py | 24 +++--- setup.py | 1 + 7 files changed, 185 insertions(+), 111 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 6a7b4d328..259be7688 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -38,7 +38,7 @@ from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IRequestFactory -from pyramid.interfaces import IRequestHandlers +from pyramid.interfaces import ITweens from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -719,12 +719,11 @@ class Configurator(object): if settings: includes = settings.get('pyramid.include', '') includes = [x.strip() for x in includes.splitlines()] - expl_handler_factories = settings.get('pyramid.request_handlers','') - expl_handler_factories = [x.strip() for x in - expl_handler_factories.splitlines()] + expl_tweens = settings.get('pyramid.tweens','') + expl_tweens = [x.strip() for x in expl_tweens.splitlines()] else: includes = [] - expl_handler_factories = [] + expl_tweens = [] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -735,10 +734,10 @@ class Configurator(object): from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) # add a handler manager - handler_manager = RequestHandlers() - registry.registerUtility(handler_manager, IRequestHandlers) - self._add_request_handler('pyramid.router.exc_view_handler_factory', - explicit=False) + tweens = Tweens() + registry.registerUtility(tweens, ITweens) + self._add_tween('pyramid.router.exc_view_tween_factory', + explicit=False) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) @@ -785,8 +784,8 @@ class Configurator(object): if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) self.commit() - for factory in expl_handler_factories: - self._add_request_handler(factory, explicit=True) + for factory in expl_tweens: + self._add_tween(factory, explicit=True) for inc in includes: self.include(inc) @@ -908,31 +907,32 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_request_handler(self, handler_factory): + def add_tween(self, tween_factory): """ - Add a request handler factory. A request handler factory is used to - wrap the Pyramid router's main request handling function. This is a - feature that may be used by framework extensions, to provide, for - example, view timing support and as a convenient place to hang + Add a 'tween factory'. A 'tween' (think: 'between') is a bit of code + that sits between the Pyramid router's main request handling function + and the upstream WSGI component that uses :app:`Pyramid` as its + 'app'. This is a feature that may be used by framework extensions, + to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to - the WSGI server. Request handlers behave a bit like :mod:`WSGI` + the upstream WSGI application. Tweens behave a bit like :mod:`WSGI` 'middleware' but they have the benefit of running in a context in - which they have access tot he Pyramid :term:`application registry` as + which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. - A request handler factory (passed as ``handler_factory``) must be a - callable (or a :term:`dotted Python name` to such a callable) which - accepts two arguments: ``handler`` and ``registry``. ``handler`` - will be the request handler being wrapped. ``registry`` will be the - Pyramid :term:`application registry` represented by this - Configurator. A request handler factory must return a request - handler when it is called. + A tween factory (passed as ``tween_factory``) must be a callable (or + a :term:`dotted Python name` to such a callable) which accepts two + arguments: ``handler`` and ``registry``. ``handler`` will be the + either the main Pyramid request handling function or another tween + (if more than one tween is configured into the request handling + chain). ``registry`` will be the Pyramid :term:`application + registry` represented by this Configurator. A tween factory must + return a tween when it is called. - A request handler accepts a :term:`request` object and returns a - :term:`response` object. + A tween accepts a :term:`request` object and returns a two-tuple + consisting of a :term:`request` object and a :term:`response` object. - Here's an example creating a handler factory and registering the - handler factory: + Here's an example creating a tween factory and registering it: .. code-block:: python @@ -942,83 +942,92 @@ class Configurator(object): log = logging.getLogger(__name__) - def timing_handler_factory(handler, registry): + def timing_tween_factory(handler, registry): if asbool(registry.settings.get('do_timing')): # if timing support is enabled, return a wrapper - def timing_handler(request): + def timing_tween(request): start = time.time() try: - response = handler(request) + request, response = handler(request) finally: end = time.time() log.debug('The request took %s seconds' % (end - start)) - return response - return timing_handler + return request, response + return timing_tween # if timing support is not enabled, return the original # handler return handler - config.add_request_handler(timing_handler_factory) + config.add_tween(timing_tween_factory) - The ``request`` argument to the handler will be the request created - by Pyramid's router when it receives a WSGI request. + The ``request`` argument to a tween will be the request created by + Pyramid's router when it receives a WSGI request. - If more than one call to ``add_request_handler`` is made within a - single application configuration, request handlers will be chained - together. The first request handler factory added will be called - with the default Pyramid request handler as its ``handler`` argument, - the second handler factory added will be called with the result of - the first handler factory, and so on, ad infinitum. The Pyramid - router will use the outermost handler produced by this chain (the - very last handler added) as its handler function. + If more than one call to ``add_tween`` is made within a single + application configuration, the added tweens will be chained together. + The first tween factory added will be called with the default Pyramid + request handler as its ``handler`` argument, the second tween factory + added will be called with the result of the first tween factory as + its ``handler`` argument, and so on, ad infinitum. The Pyramid router + will use the outermost tween produced by this chain (the very last + tween added) as its request handler function. By default, the ordering of the chain is controlled entirely by the - relative ordering of calls to ``add_request_handler``. However, the - deploying user can override request handler inclusion and ordering in - his Pyramid configuration using the ``pyramid.request_handlers`` - settings value. When used, this settings value should be a list of - Python dotted names which imply the ordering (and inclusion) of - handler factories in the request handler chain. - - Pyramid will prevent the same request handler factory from being - added to the request handling chain more than once using - configuration conflict detection. If you wish to add the same - handler factory more than once in a configuration, you should either: - a) use a handler that is an *instance* rather than a function or - class, or b) use a function or class with the same logic as the other - it conflicts with but with a different ``__name__`` attribute. - - A user can get a representation of both the implicit request handler - ordering (the ordering specified by calls to ``add_request_handler``) - and the explicit request handler ordering (specified by the - ``pyramid.request_handlers`` setting) orderings using the ``paster - phandlers`` command. Handler factories which are functions or - classes will show up as a standard Python dotted name in the - ``phandlers`` output. Handler factories which are *instances* will - show their module and class name; the Python object id of the - instance will be appended. + relative ordering of calls to ``add_tween``. However, the deploying + user can override tween inclusion and ordering in his Pyramid + configuration using the ``pyramid.tweens`` settings value. When + used, this settings value should be a list of Python dotted names + which imply the ordering (and inclusion) of tween factories in the + tween chain. + + Pyramid will prevent the same tween factory from being added to the + tween chain more than once using configuration conflict detection. + If you wish to add the same tween factory more than once in a + configuration, you should either: a) use a tween factory that is an + *instance* rather than a function or class, or b) use a function or + class as a tween factory with the same logic as the other tween + factory it conflicts with but with a different ``__name__`` + attribute. + + A user can get a representation of both the implicit tween ordering + (the ordering specified by calls to ``add_tween``) and the explicit + request handler ordering (specified by the ``pyramid.tweens`` + setting) orderings using the ``paster ptweens`` command. Handler + factories which are functions or classes will show up as a standard + Python dotted name in the ``ptweens`` output. Tween factories + which are *instances* will show their module and class name; the + Python object id of the instance will be appended. .. note:: This feature is new as of Pyramid 1.1.1. """ - return self._add_request_handler(handler_factory, explicit=False) + return self._add_tween(tween_factory, explicit=False) - def _add_request_handler(self, handler_factory, explicit): - handler_factory = self.maybe_dotted(handler_factory) - if hasattr(handler_factory, '__name__'): + # XXX temporary bw compat for debugtoolbar + def add_request_handler(self, factory, name): + return self._add_tween(factory, explicit=False) + + def _add_tween(self, tween_factory, explicit): + tween_factory = self.maybe_dotted(tween_factory) + if (hasattr(tween_factory, '__name__') and + hasattr(tween_factory, '__module__')): # function or class - name = '.'.join((handler_factory.__module__, - handler_factory.__name__)) - else: + name = '.'.join([tween_factory.__module__, + tween_factory.__name__]) + elif hasattr(tween_factory, '__module__'): # instance - name = '.'.join(handler_factory.__module__, - handler_factory.__class__.__name__, - str(id(handler_factory))) + name = '.'.join([tween_factory.__module__, + tween_factory.__class__.__name__, + str(id(tween_factory))]) + else: + raise ConfigurationError( + 'A tween factory must be a class, an instance, or a function; ' + '%s is not a suitable tween factory' % tween_factory) def register(): registry = self.registry - handler_manager = registry.getUtility(IRequestHandlers) - handler_manager.add(name, handler_factory, explicit) - self.action(('request_handler', name), register) + handler_manager = registry.getUtility(ITweens) + handler_manager.add(name, tween_factory, explicit) + self.action(('tween', name), register) @action_method def add_subscriber(self, subscriber, iface=None): @@ -3455,8 +3464,8 @@ def isexception(o): global_registries = WeakOrderedSet() -class RequestHandlers(object): - implements(IRequestHandlers) +class Tweens(object): + implements(ITweens) def __init__(self): self.explicit = [] self.implicit = [] @@ -3468,10 +3477,10 @@ class RequestHandlers(object): self.implicit.append((name, factory)) def __call__(self, handler, registry): - handler_factories = self.implicit + factories = self.implicit if self.explicit: - handler_factories = self.explicit - for name, factory in handler_factories: + factories = self.explicit + for name, factory in factories: handler = factory(handler, registry) return handler diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index c4bacd46d..d97632018 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -446,9 +446,9 @@ class IMultiDict(Interface): # docs-only interface class IRequest(Interface): """ Request type interface attached to all request objects """ -class IRequestHandlers(Interface): +class ITweens(Interface): """ Marker interface for utility registration representing the ordered - set of a configuration's request handler factories""" + set of a configuration's tween factories""" class IRequestHandler(Interface): """ """ diff --git a/pyramid/paster.py b/pyramid/paster.py index 3143fa91e..54f5a51a6 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -9,8 +9,8 @@ from paste.deploy import loadapp from paste.script.command import Command from pyramid.interfaces import IMultiView +from pyramid.interfaces import ITweens -from pyramid.scripting import get_root from pyramid.scripting import prepare from pyramid.util import DottedNameResolver @@ -534,3 +534,67 @@ class PViewsCommand(PCommand): self.out(" Not found.") self.out('') + +class PTweensCommand(PCommand): + """Print all implicit and explicit :term:`tween` objects used by a + Pyramid application. The handler output includes whether the system is + using an explicit tweens ordering (will be true when the + ``pyramid.tweens`` setting is used) or an implicit tweens ordering (will + be true when the ``pyramid.tweens`` setting is *not* used). + + This command accepts one positional argument: + + ``config_uri`` -- specifies the PasteDeploy config file to use for the + interactive shell. The format is ``inifile#name``. If the name is left + off, ``main`` will be assumed. + + Example:: + + $ paster ptweens myapp.ini#main + + """ + summary = "Print all tweens related to a Pyramid application" + min_args = 1 + max_args = 1 + stdout = sys.stdout + + parser = Command.standard_parser(simulate=True) + + def _get_tweens(self, registry): + from pyramid.config import Configurator + config = Configurator(registry = registry) + return config.registry.queryUtility(ITweens) + + def out(self, msg): # pragma: no cover + print msg + + def command(self): + config_uri = self.args[0] + env = self.bootstrap[0](config_uri) + registry = env['registry'] + tweens = self._get_tweens(registry) + if tweens is not None: + ordering = [] + if tweens.explicit: + self.out('"pyramid.tweens" config value set ' + '(explicitly ordered tweens used)') + self.out('') + ordering.append((tweens.explicit, + 'Explicit Tween Chain (used)')) + ordering.append((tweens.implicit, + 'Implicit Tween Chain (not used)')) + else: + self.out('"pyramid.tweens" config value NOT set ' + '(implicitly ordered tweens used)') + self.out('') + ordering.append((tweens.implicit, '')) + for L, title in ordering: + if title: + self.out(title) + self.out('') + fmt = '%-8s %-30s' + self.out(fmt % ('Position', 'Name')) + self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) + for pos, (name, item) in enumerate(L): + self.out(fmt % (pos, name)) + self.out('') diff --git a/pyramid/router.py b/pyramid/router.py index d8f5a08da..8cac733fb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -14,7 +14,7 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IRequestHandlers +from pyramid.interfaces import ITweens from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -39,12 +39,12 @@ class Router(object): self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) - handler_manager = q(IRequestHandlers) - if handler_manager is None: - self.handle_request = exc_view_handler_factory(self.handle_request, - registry) + tweens = q(ITweens) + if tweens is None: + self.handle_request = exc_view_tween_factory(self.handle_request, + registry) else: - self.handle_request = handler_manager(self.handle_request, registry) + self.handle_request = tweens(self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry @@ -190,7 +190,7 @@ class Router(object): finally: manager.pop() -def exc_view_handler_factory(handler, registry): +def exc_view_tween_factory(handler, registry): has_listeners = registry.has_listeners adapters = registry.adapters notify = registry.notify diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ebc3f85cc..ef2bb24d7 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -336,7 +336,7 @@ class ConfiguratorTests(unittest.TestCase): reg = DummyRegistry() config = self._makeOne(reg) config.add_view = lambda *arg, **kw: False - config._add_request_handler = lambda *arg, **kw: False + config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(reg.has_listeners, True) @@ -348,7 +348,7 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(reg) views = [] config.add_view = lambda *arg, **kw: views.append((arg, kw)) - config._add_request_handler = lambda *arg, **kw: False + config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 6921de179..6b0354468 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -134,13 +134,16 @@ class TestRouter(unittest.TestCase): router = self._makeOne() self.assertEqual(router.request_factory, DummyRequestFactory) - def test_request_handler_factories(self): - from pyramid.interfaces import IRequestHandlers - from pyramid.config import RequestHandlers - handler_manager = RequestHandlers() - self.registry.registerUtility(handler_manager, IRequestHandlers) + def test_tween_factories(self): + from pyramid.interfaces import ITweens + from pyramid.config import Tweens + from pyramid.response import Response + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IResponse + tweens = Tweens() + self.registry.registerUtility(tweens, ITweens) L = [] - def handler_factory1(handler, registry): + def tween_factory1(handler, registry): L.append((handler, registry)) def wrapper(request): request.environ['handled'].append('one') @@ -148,7 +151,7 @@ class TestRouter(unittest.TestCase): wrapper.name = 'one' wrapper.child = handler return wrapper - def handler_factory2(handler, registry): + def tween_factory2(handler, registry): L.append((handler, registry)) def wrapper(request): request.environ['handled'] = ['two'] @@ -156,16 +159,13 @@ class TestRouter(unittest.TestCase): wrapper.name = 'two' wrapper.child = handler return wrapper - handler_manager.add('one', handler_factory1) - handler_manager.add('two', handler_factory2) + tweens.add('one', tween_factory1) + tweens.add('two', tween_factory2) router = self._makeOne() self.assertEqual(router.handle_request.name, 'two') self.assertEqual(router.handle_request.child.name, 'one') self.assertEqual(router.handle_request.child.child.__name__, 'handle_request') - from pyramid.response import Response - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import IResponse context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() diff --git a/setup.py b/setup.py index 109be6951..5e362ea86 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,7 @@ setup(name='pyramid', pshell=pyramid.paster:PShellCommand proutes=pyramid.paster:PRoutesCommand pviews=pyramid.paster:PViewsCommand + ptweens=pyramid.paster:PTweensCommand [console_scripts] bfg2pyramid = pyramid.fixers.fix_bfg_imports:main """ -- cgit v1.2.3 From dc7122f8bb5033d0cd0e95f5adfa66c4499f140e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 17:41:30 -0400 Subject: add glossary entry for tween --- docs/glossary.rst | 11 +++++++++++ pyramid/config.py | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index c6705fdc5..ccb62bbc8 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -917,3 +917,14 @@ Glossary PyPy is an "alternative implementation of the Python language":http://pypy.org/ + tween + A bit of code that sits between the Pyramid router's main request + handling function and the upstream WSGI component that uses + :app:`Pyramid` as its 'app'. A tween may be used by Pyramid framework + extensions, to provide, for example, Pyramid-specific view timing + support bookkeeping code that examines exceptions before they are + returned to the upstream WSGI application. Tweens behave a bit like + :mod:`WSGI` 'middleware' but they have the benefit of running in a + context in which they have access to the Pyramid :term:`application + registry` as well as the Pyramid rendering machinery. + diff --git a/pyramid/config.py b/pyramid/config.py index 259be7688..b8382e6a7 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -909,16 +909,16 @@ class Configurator(object): @action_method def add_tween(self, tween_factory): """ - Add a 'tween factory'. A 'tween' (think: 'between') is a bit of code - that sits between the Pyramid router's main request handling function - and the upstream WSGI component that uses :app:`Pyramid` as its - 'app'. This is a feature that may be used by framework extensions, - to provide, for example, Pyramid-specific view timing support - bookkeeping code that examines exceptions before they are returned to - the upstream WSGI application. Tweens behave a bit like :mod:`WSGI` - 'middleware' but they have the benefit of running in a context in - which they have access to the Pyramid :term:`application registry` as - well as the Pyramid rendering machinery. + Add a 'tween factory'. A :term`tween` (think: 'between') is a bit of + code that sits between the Pyramid router's main request handling + function and the upstream WSGI component that uses :app:`Pyramid` as + its 'app'. This is a feature that may be used by Pyramid framework + extensions, to provide, for example, Pyramid-specific view timing + support bookkeeping code that examines exceptions before they are + returned to the upstream WSGI application. Tweens behave a bit like + :mod:`WSGI` 'middleware' but they have the benefit of running in a + context in which they have access to the Pyramid :term:`application + registry` as well as the Pyramid rendering machinery. A tween factory (passed as ``tween_factory``) must be a callable (or a :term:`dotted Python name` to such a callable) which accepts two -- cgit v1.2.3 From dccaa2ff3dec57a1166ad10b545336b227a1f9d7 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 17:43:42 -0400 Subject: add glossary entry for tween --- pyramid/tests/test_config.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index ef2bb24d7..3e4895277 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -614,21 +614,16 @@ pyramid.tests.test_config.dummy_include2""", settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) - ## def test_add_request_handlers_names_distinct(self): - ## from pyramid.interfaces import IRequestHandlerFactories - ## from pyramid.interfaces import IRequestHandlerFactory - ## def factory1(handler, registry): return handler - ## def factory2(handler, registry): return handler - ## config = self._makeOne() - ## config.add_request_handler(factory1, 'name1') - ## config.add_request_handler(factory2, 'name2') - ## config.commit() - ## names = config.registry.queryUtility(IRequestHandlerFactories) - ## self.assertEqual(names, ['name1', 'name2']) - ## f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - ## f2 = config.registry.getUtility(IRequestHandlerFactory, name='name2') - ## self.assertEqual(f1, factory1) - ## self.assertEqual(f2, factory2) + def test_add_tweens_names_distinct(self): + from pyramid.interfaces import ITweens + def factory1(handler, registry): return handler + def factory2(handler, registry): return handler + config = self._makeOne() + config.add_tween(factory1) + config.add_tween(factory2) + config.commit() + tweens = config.registry.queryUtility(ITweens) + self.assertEqual(tweens.implicit, None) ## def test_add_request_handlers_dottednames(self): ## import pyramid.tests -- cgit v1.2.3 From 46adcd6bd42318bea3c84bc8d86e3395d6d026d9 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 17:52:57 -0400 Subject: tests for add_tween --- pyramid/config.py | 3 +-- pyramid/router.py | 6 ++--- pyramid/tests/test_config.py | 58 +++++++++++++++++++++++--------------------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index b8382e6a7..33e9db6a1 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -736,8 +736,7 @@ class Configurator(object): # add a handler manager tweens = Tweens() registry.registerUtility(tweens, ITweens) - self._add_tween('pyramid.router.exc_view_tween_factory', - explicit=False) + self._add_tween('pyramid.router.excview_tween_factory', explicit=False) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) diff --git a/pyramid/router.py b/pyramid/router.py index 8cac733fb..dcf828377 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -41,8 +41,8 @@ class Router(object): self.request_factory = q(IRequestFactory, default=Request) tweens = q(ITweens) if tweens is None: - self.handle_request = exc_view_tween_factory(self.handle_request, - registry) + self.handle_request = excview_tween_factory(self.handle_request, + registry) else: self.handle_request = tweens(self.handle_request, registry) @@ -190,7 +190,7 @@ class Router(object): finally: manager.pop() -def exc_view_tween_factory(handler, registry): +def excview_tween_factory(handler, registry): has_listeners = registry.has_listeners adapters = registry.adapters notify = registry.notify diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 3e4895277..7bf0e632f 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -616,6 +616,7 @@ pyramid.tests.test_config.dummy_include2""", def test_add_tweens_names_distinct(self): from pyramid.interfaces import ITweens + from pyramid.router import excview_tween_factory def factory1(handler, registry): return handler def factory2(handler, registry): return handler config = self._makeOne() @@ -623,34 +624,33 @@ pyramid.tests.test_config.dummy_include2""", config.add_tween(factory2) config.commit() tweens = config.registry.queryUtility(ITweens) - self.assertEqual(tweens.implicit, None) - - ## def test_add_request_handlers_dottednames(self): - ## import pyramid.tests - ## from pyramid.interfaces import IRequestHandlerFactories - ## from pyramid.interfaces import IRequestHandlerFactory - ## config = self._makeOne() - ## config.add_request_handler('pyramid.tests', 'name1') - ## config.commit() - ## names = config.registry.queryUtility(IRequestHandlerFactories) - ## self.assertEqual(names, ['name1']) - ## f1 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - ## self.assertEqual(f1, pyramid.tests) - - ## def test_add_request_handlers_names_overlap(self): - ## from pyramid.interfaces import IRequestHandlerFactories - ## from pyramid.interfaces import IRequestHandlerFactory - ## def factory1(handler, registry): return handler - ## def factory2(handler, registry): return handler - ## def factory3(handler, registry): return handler - ## config = self._makeOne(autocommit=True) - ## config.add_request_handler(factory1, 'name1') - ## config.add_request_handler(factory2, 'name2') - ## config.add_request_handler(factory3, 'name1') - ## names = config.registry.queryUtility(IRequestHandlerFactories) - ## self.assertEqual(names, ['name1', 'name2', 'name1']) - ## f3 = config.registry.getUtility(IRequestHandlerFactory, name='name1') - ## self.assertEqual(f3, factory3) + self.assertEqual( + tweens.implicit, + [('pyramid.router.excview_tween_factory', excview_tween_factory), + ('pyramid.tests.test_config.factory1', factory1), + ('pyramid.tests.test_config.factory2', factory2)]) + + def test_add_tween_dottedname(self): + from pyramid.interfaces import ITweens + from pyramid.router import excview_tween_factory + config = self._makeOne() + config.add_tween('pyramid.tests.test_config.dummy_tween_factory') + config.commit() + tweens = config.registry.queryUtility(ITweens) + self.assertEqual( + tweens.implicit, + [ + ('pyramid.router.excview_tween_factory', excview_tween_factory), + ('pyramid.tests.test_config.dummy_tween_factory', + dummy_tween_factory) + ]) + + def test_add_tweens_conflict(self): + from zope.configuration.config import ConfigurationConflictError + config = self._makeOne() + config.add_tween('pyramid.tests.test_config.dummy_tween_factory') + config.add_tween('pyramid.tests.test_config.dummy_tween_factory') + self.assertRaises(ConfigurationConflictError, config.commit) def test_add_subscriber_defaults(self): from zope.interface import implements @@ -5660,3 +5660,5 @@ from pyramid.interfaces import IResponse class DummyResponse(object): implements(IResponse) +def dummy_tween_factory(handler, registry): + pass -- cgit v1.2.3 From feabd37caa3f860b550da904165ccb17252f2bf7 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 18:00:57 -0400 Subject: add setup_registry test --- pyramid/config.py | 15 ++++++++------- pyramid/tests/test_config.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 33e9db6a1..9136c0880 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -719,11 +719,11 @@ class Configurator(object): if settings: includes = settings.get('pyramid.include', '') includes = [x.strip() for x in includes.splitlines()] - expl_tweens = settings.get('pyramid.tweens','') - expl_tweens = [x.strip() for x in expl_tweens.splitlines()] + tweens = settings.get('pyramid.tweens','') + tweens = [x.strip() for x in tweens.splitlines()] else: includes = [] - expl_tweens = [] + tweens = [] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -734,10 +734,13 @@ class Configurator(object): from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) # add a handler manager - tweens = Tweens() - registry.registerUtility(tweens, ITweens) + tweenreg = Tweens() + registry.registerUtility(tweenreg, ITweens) self._add_tween('pyramid.router.excview_tween_factory', explicit=False) + for factory in tweens: + self._add_tween(factory, explicit=True) + if debug_logger is None: debug_logger = logging.getLogger(self.package_name) elif isinstance(debug_logger, basestring): @@ -783,8 +786,6 @@ class Configurator(object): if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) self.commit() - for factory in expl_tweens: - self._add_tween(factory, explicit=True) for inc in includes: self.include(inc) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 7bf0e632f..0761498fb 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -583,6 +583,20 @@ pyramid.tests.test_config.dummy_include2""", self.assert_(reg.included) self.assert_(reg.also_included) + def test_setup_registry_tweens(self): + from pyramid.interfaces import ITweens + from pyramid.registry import Registry + reg = Registry() + config = self._makeOne(reg) + settings = { + 'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory' + } + config.setup_registry(settings=settings) + tweens = config.registry.getUtility(ITweens) + self.assertEqual(tweens.explicit, + [('pyramid.tests.test_config.dummy_tween_factory', + dummy_tween_factory)]) + def test_get_settings_nosettings(self): from pyramid.registry import Registry reg = Registry() -- cgit v1.2.3 From 3e15222b7d92e55f1c53ee252d3bebf25944e216 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 18:13:52 -0400 Subject: test Tweens --- pyramid/config.py | 9 +++--- pyramid/tests/test_config.py | 65 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 9136c0880..db5e5f945 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1003,10 +1003,6 @@ class Configurator(object): """ return self._add_tween(tween_factory, explicit=False) - # XXX temporary bw compat for debugtoolbar - def add_request_handler(self, factory, name): - return self._add_tween(factory, explicit=False) - def _add_tween(self, tween_factory, explicit): tween_factory = self.maybe_dotted(tween_factory) if (hasattr(tween_factory, '__name__') and @@ -1029,6 +1025,11 @@ class Configurator(object): handler_manager.add(name, tween_factory, explicit) self.action(('tween', name), register) + # XXX temporary bw compat for debugtoolbar + @action_method + def add_request_handler(self, factory, name): # pragma: no cover + return self._add_tween(factory, explicit=False) + @action_method def add_subscriber(self, subscriber, iface=None): """Add an event :term:`subscriber` for the event stream diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 0761498fb..8b11bb22a 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -659,6 +659,29 @@ pyramid.tests.test_config.dummy_include2""", dummy_tween_factory) ]) + def test_add_tween_instance(self): + from pyramid.interfaces import ITweens + from pyramid.router import excview_tween_factory + class ATween(object): pass + atween = ATween() + config = self._makeOne() + config.add_tween(atween) + config.commit() + tweens = config.registry.queryUtility(ITweens) + self.assertEqual(len(tweens.implicit), 2) + self.assertEqual( + tweens.implicit[0], + ('pyramid.router.excview_tween_factory', excview_tween_factory)) + self.assertTrue( + tweens.implicit[1][0].startswith('pyramid.tests.test_config.ATween.')) + self.assertEqual(tweens.implicit[1][1], atween) + + def test_add_tween_unsuitable(self): + from pyramid.exceptions import ConfigurationError + import pyramid.tests + config = self._makeOne() + self.assertRaises(ConfigurationError, config.add_tween, pyramid.tests) + def test_add_tweens_conflict(self): from zope.configuration.config import ConfigurationConflictError config = self._makeOne() @@ -5488,6 +5511,45 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) +class TestTweens(unittest.TestCase): + def _makeOne(self): + from pyramid.config import Tweens + return Tweens() + + def test_add_explicit(self): + tweens = self._makeOne() + tweens.add('name', 'factory', explicit=True) + self.assertEqual(tweens.explicit, [('name', 'factory')]) + tweens.add('name2', 'factory2', explicit=True) + self.assertEqual(tweens.explicit, [('name', 'factory'), + ('name2', 'factory2')]) + + def test_add_implicit(self): + tweens = self._makeOne() + tweens.add('name', 'factory', explicit=False) + self.assertEqual(tweens.implicit, [('name', 'factory')]) + tweens.add('name2', 'factory2', explicit=False) + self.assertEqual(tweens.implicit, [('name', 'factory'), + ('name2', 'factory2')]) + + def test___call___explicit(self): + tweens = self._makeOne() + def factory1(handler, registry): + return handler + def factory2(handler, registry): + return '123' + tweens.explicit = [('name', factory1), ('name', factory2)] + self.assertEqual(tweens(None, None), '123') + + def test___call___implicit(self): + tweens = self._makeOne() + def factory1(handler, registry): + return handler + def factory2(handler, registry): + return '123' + tweens.implicit = [('name', factory1), ('name', factory2)] + self.assertEqual(tweens(None, None), '123') + class DummyRequest: subpath = () matchdict = None @@ -5674,5 +5736,4 @@ from pyramid.interfaces import IResponse class DummyResponse(object): implements(IResponse) -def dummy_tween_factory(handler, registry): - pass +def dummy_tween_factory(handler, registry): pass -- cgit v1.2.3 From 19466d028c1139445eb249c0363cafef5f870bdf Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 18:15:24 -0400 Subject: remove temp bw compat; the return signature of tweens has changed anyway --- pyramid/config.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index db5e5f945..c1adb51c9 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -1025,11 +1025,6 @@ class Configurator(object): handler_manager.add(name, tween_factory, explicit) self.action(('tween', name), register) - # XXX temporary bw compat for debugtoolbar - @action_method - def add_request_handler(self, factory, name): # pragma: no cover - return self._add_tween(factory, explicit=False) - @action_method def add_subscriber(self, subscriber, iface=None): """Add an event :term:`subscriber` for the event stream -- cgit v1.2.3 From 1dc57d53c54338df467e6c1e8201589eab73022f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 18:21:11 -0400 Subject: docs --- pyramid/config.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index c1adb51c9..918e3bce4 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -909,14 +909,14 @@ class Configurator(object): @action_method def add_tween(self, tween_factory): """ - Add a 'tween factory'. A :term`tween` (think: 'between') is a bit of - code that sits between the Pyramid router's main request handling + Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit + of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses :app:`Pyramid` as its 'app'. This is a feature that may be used by Pyramid framework extensions, to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to the upstream WSGI application. Tweens behave a bit like - :mod:`WSGI` 'middleware' but they have the benefit of running in a + :term:`WSGI` 'middleware' but they have the benefit of running in a context in which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. @@ -929,8 +929,9 @@ class Configurator(object): registry` represented by this Configurator. A tween factory must return a tween when it is called. - A tween accepts a :term:`request` object and returns a two-tuple - consisting of a :term:`request` object and a :term:`response` object. + A tween is a callable which accepts a :term:`request` object and + returns a two-tuple consisting of a :term:`request` object and a + :term:`response` object. Here's an example creating a tween factory and registering it: @@ -970,25 +971,33 @@ class Configurator(object): request handler as its ``handler`` argument, the second tween factory added will be called with the result of the first tween factory as its ``handler`` argument, and so on, ad infinitum. The Pyramid router - will use the outermost tween produced by this chain (the very last - tween added) as its request handler function. + will use the outermost tween produced by this chain (the tween + generated by the very last tween factory added) as its request + handler function. By default, the ordering of the chain is controlled entirely by the relative ordering of calls to ``add_tween``. However, the deploying user can override tween inclusion and ordering in his Pyramid configuration using the ``pyramid.tweens`` settings value. When - used, this settings value should be a list of Python dotted names - which imply the ordering (and inclusion) of tween factories in the - tween chain. + used, this settings value will be a list of Python dotted names which + imply the ordering (and inclusion) of tween factories in the tween + chain. + + Note that Pyramid's own exception view handling logic is implemented + as a tween factory: ``pyramid.router.excview_tween_factory``. If + Pyramid exception view handling is desired, and explicit tween + factories are specified via ``pyramid.tweens``, this function must be + added to the ``pyramid.tweens`` setting. If it is not present, + Pyramid will not perform exception view handling. Pyramid will prevent the same tween factory from being added to the tween chain more than once using configuration conflict detection. If you wish to add the same tween factory more than once in a configuration, you should either: a) use a tween factory that is an - *instance* rather than a function or class, or b) use a function or + instance rather than a function or class, b) use a function or class as a tween factory with the same logic as the other tween factory it conflicts with but with a different ``__name__`` - attribute. + attribute or c) call config.commit() between calls to ``add_tween``. A user can get a representation of both the implicit tween ordering (the ordering specified by calls to ``add_tween``) and the explicit -- cgit v1.2.3 From 1311321d454ded6226b09f929ebc9e4aa2c12771 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 18:58:26 -0400 Subject: add tweens module, add docs for ptweens and tweens to hooks --- docs/api.rst | 1 + docs/api/config.rst | 2 +- docs/api/tweens.rst | 8 +++ docs/narr/commandline.rst | 75 ++++++++++++++++++++++++- docs/narr/hooks.rst | 128 +++++++++++++++++++++++++++++++++++++++++++ pyramid/config.py | 90 +----------------------------- pyramid/router.py | 38 +------------ pyramid/tests/test_config.py | 12 ++-- pyramid/tweens.py | 45 +++++++++++++++ 9 files changed, 265 insertions(+), 134 deletions(-) create mode 100644 docs/api/tweens.rst create mode 100644 pyramid/tweens.py diff --git a/docs/api.rst b/docs/api.rst index a7e1566d3..6ff6e9fb1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -32,6 +32,7 @@ documentation is organized alphabetically by module name. api/testing api/threadlocal api/traversal + api/tweens api/url api/view api/wsgi diff --git a/docs/api/config.rst b/docs/api/config.rst index 21e2b828d..1a9bb6ba4 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -78,7 +78,7 @@ .. automethod:: set_view_mapper - .. automethod:: add_request_handler + .. automethod:: add_tween .. automethod:: testing_securitypolicy diff --git a/docs/api/tweens.rst b/docs/api/tweens.rst new file mode 100644 index 000000000..5fc15cb00 --- /dev/null +++ b/docs/api/tweens.rst @@ -0,0 +1,8 @@ +.. _tweens_module: + +:mod:`pyramid.tweens` +--------------------- + +.. automodule:: pyramid.tweens + + .. autofunction:: excview_tween_factory diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 509af7dd3..6f969196f 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -297,8 +297,79 @@ application, nothing will be printed to the console when ``paster proutes`` is executed. .. index:: - single: scripting - single: bootstrap + pair: tweens; printing + single: paster ptweens + single: ptweens + +.. _displaying_tweens: + +Displaying "Tweens" +------------------- + +A user can get a representation of both the implicit :term:`tween` ordering +(the ordering specified by calls to +:meth:`pyramid.config.Configurator.add_tween`) and the explicit tween +ordering (specified by the ``pyramid.tweens`` configuration setting) +orderings using the ``paster ptweens`` command. Handler factories which are +functions or classes will show up as a standard Python dotted name in the +``paster ptweens`` output. Tween factories which are *instances* will show +their module and class name; the Python object id of the instance will be +appended. + +For example, here's the ``paster pwteens`` command run against a system +configured without any explicit tweens: + +.. code-block:: text + :linenos: + + [chrism@thinko starter]$ ../bin/paster ptweens development.ini + "pyramid.tweens" config value NOT set (implicitly ordered tweens used) + + Position Name + -------- ---- + 0 pyramid.router.excview_tween_factory + +Here's the ``paster pwteens`` command run against a system configured *with* +explicit tweens defined in its ``development.ini`` file: + +.. code-block:: text + :linenos: + + [chrism@thinko starter]$ ../bin/paster ptweens development.ini + "pyramid.tweens" config value set (explicitly ordered tweens used) + + Explicit Tween Chain (used) + + Position Name + -------- ---- + 0 pyramid.tweens.excview_tween_factory + 1 starter.tween_factory1 + 2 starter.tween_factory2 + + Implicit Tween Chain (not used) + + Position Name + -------- ---- + 0 pyramid.tweens.excview_tween_factory + +Here's the application configuration section of the ``development.ini`` used +by the above ``paster ptweens`` command which reprorts that the explicit +tween chain is used: + +.. code-block:: text + :linenos: + + [app:starter] + use = egg:starter + pyramid.reload_templates = true + pyramid.debug_authorization = false + pyramid.debug_notfound = false + pyramid.debug_routematch = false + pyramid.debug_templates = true + pyramid.default_locale_name = en + pyramid.tweens = pyramid.tweens.excview_tween_factory + starter.tween_factory1 + starter.tween_factory2 .. _writing_a_script: diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 4f493c854..7290876e6 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -828,3 +828,131 @@ performed, enabling you to set up the utility in advance: For full details, please read the `Venusian documentation `_. +.. _registering_tweens: + +Registering "Tweens" +-------------------- + +.. note:: Tweens are a feature which were added in Pyramid 1.1.1. They are + not available in any previous version. + +A :term:`tween` (think: "between") is a bit of code that sits between the +Pyramid router's main request handling function and the upstream WSGI +component that uses :app:`Pyramid` as its "app". This is a feature that may +be used by Pyramid framework extensions, to provide, for example, +Pyramid-specific view timing support bookkeeping code that examines +exceptions before they are returned to the upstream WSGI application. Tweens +behave a bit like :term:`WSGI` middleware but they have the benefit of +running in a context in which they have access to the Pyramid +:term:`application registry` as well as the Pyramid rendering machinery. + +To make use of tweens, you must construct a "tween factory". A tween factory +must be a callable (or a :term:`dotted Python name` to such a callable) which +accepts two arguments: ``handler`` and ``registry``. ``handler`` will be the +either the main Pyramid request handling function or another tween (if more +than one tween is configured into the request handling chain). ``registry`` +will be the Pyramid :term:`application registry` represented by this +Configurator. A tween factory must return a tween when it is called. + +A tween is a callable which accepts a :term:`request` object and returns a +two-tuple consisting of a :term:`request` object and a :term:`response` +object. + +Once you've created a tween factory, you can register it using the +:meth:`pyramid.config.Configurator.add_tween` method. + +Here's an example creating a tween factory and registering it: + +.. code-block:: python + :lineno: + + import time + from pyramid.settings import asbool + import logging + + log = logging.getLogger(__name__) + + def timing_tween_factory(handler, registry): + if asbool(registry.settings.get('do_timing')): + # if timing support is enabled, return a wrapper + def timing_tween(request): + start = time.time() + try: + request, response = handler(request) + finally: + end = time.time() + log.debug('The request took %s seconds' % + (end - start)) + return request, response + return timing_tween + # if timing support is not enabled, return the original + # handler + return handler + + from pyramid.config import Configurator + + config = Configurator() + config.add_tween(timing_tween_factory) + +The ``request`` argument to a tween will be the request created by Pyramid's +router when it receives a WSGI request. + +If more than one call to :meth:`pyramid.config.Configurator.add_tween` is +made within a single application configuration, the added tweens will be +chained together. The first tween factory added will be called with the +default Pyramid request handler as its ``handler`` argument, the second tween +factory added will be called with the result of the first tween factory as +its ``handler`` argument, and so on, ad infinitum. The Pyramid router will +use the outermost tween produced by this chain (the tween generated by the +very last tween factory added) as its request handler function. + +Pyramid will prevent the same tween factory from being added to the implicit +tween chain more than once using configuration conflict detection. If you +wish to add the same tween factory more than once in a configuration, you +should either: a) use a tween factory that is an instance rather than a +function or class, b) use a function or class as a tween factory with the +same logic as the other tween factory it conflicts with but with a different +``__name__`` attribute or c) call :meth:`pyramid.config.Configurator.commit` +between calls to :meth:`pyramid.config.Configurator.add_tween`. + +By default, the ordering of the chain is controlled entirely by the relative +ordering of calls to :meth:`pyramid.config.Configurator.add_tween`. However, +the deploying user can override tween inclusion and ordering entirely in his +Pyramid configuration using the ``pyramid.tweens`` settings value. When +used, this settings value will be a list of Python dotted names which imply +the ordering (and inclusion) of tween factories in the tween chain. For +example: + +.. code-block:: ini + :linenos: + + [app:main] + use = egg:MyApp + pyramid.reload_templates = true + pyramid.debug_authorization = false + pyramid.debug_notfound = false + pyramid.debug_routematch = false + pyramid.debug_templates = true + pyramid.tweens = pyramid.tweens.excview_tween_factory + myapp.my_cool_tween_factory + +In the above configuration, calls made during configuration to +:meth:`pyramid.config.Configurator.add_tween` are ignored, and the user is +telling the system to use the tween factories he has listed in the +``pyramid.tweens`` configuration setting (each is a:term:`Python dotted name` +which points to a tween factory). The *last* tween factory in the +``pyramid.tweens`` list will be used as the producer of the effective +:app:`Pyramid` request handling function; it will wrap the tween factory +declared directly "above" it, ad infinitum. + +.. note:: Pyramid's own :term:`exception view` handling logic is implemented + as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`. + If Pyramid exception view handling is desired, and tween factories are + specified via the ``pyramid.tweens`` configuration setting, the + :func:`pyramid.tweens.excview_tween_factory` function must be added to the + ``pyramid.tweens`` configuration setting list explicitly. If it is not + present, Pyramid will not perform exception view handling. + +The ``paster ptweens`` command-line utility can be used to report the current +tween chain used by an application. See :ref:`displaying_tweens`. + diff --git a/pyramid/config.py b/pyramid/config.py index 918e3bce4..ebbea35ea 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -920,93 +920,7 @@ class Configurator(object): context in which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. - A tween factory (passed as ``tween_factory``) must be a callable (or - a :term:`dotted Python name` to such a callable) which accepts two - arguments: ``handler`` and ``registry``. ``handler`` will be the - either the main Pyramid request handling function or another tween - (if more than one tween is configured into the request handling - chain). ``registry`` will be the Pyramid :term:`application - registry` represented by this Configurator. A tween factory must - return a tween when it is called. - - A tween is a callable which accepts a :term:`request` object and - returns a two-tuple consisting of a :term:`request` object and a - :term:`response` object. - - Here's an example creating a tween factory and registering it: - - .. code-block:: python - - import time - from pyramid.settings import asbool - import logging - - log = logging.getLogger(__name__) - - def timing_tween_factory(handler, registry): - if asbool(registry.settings.get('do_timing')): - # if timing support is enabled, return a wrapper - def timing_tween(request): - start = time.time() - try: - request, response = handler(request) - finally: - end = time.time() - log.debug('The request took %s seconds' % - (end - start)) - return request, response - return timing_tween - # if timing support is not enabled, return the original - # handler - return handler - - config.add_tween(timing_tween_factory) - - The ``request`` argument to a tween will be the request created by - Pyramid's router when it receives a WSGI request. - - If more than one call to ``add_tween`` is made within a single - application configuration, the added tweens will be chained together. - The first tween factory added will be called with the default Pyramid - request handler as its ``handler`` argument, the second tween factory - added will be called with the result of the first tween factory as - its ``handler`` argument, and so on, ad infinitum. The Pyramid router - will use the outermost tween produced by this chain (the tween - generated by the very last tween factory added) as its request - handler function. - - By default, the ordering of the chain is controlled entirely by the - relative ordering of calls to ``add_tween``. However, the deploying - user can override tween inclusion and ordering in his Pyramid - configuration using the ``pyramid.tweens`` settings value. When - used, this settings value will be a list of Python dotted names which - imply the ordering (and inclusion) of tween factories in the tween - chain. - - Note that Pyramid's own exception view handling logic is implemented - as a tween factory: ``pyramid.router.excview_tween_factory``. If - Pyramid exception view handling is desired, and explicit tween - factories are specified via ``pyramid.tweens``, this function must be - added to the ``pyramid.tweens`` setting. If it is not present, - Pyramid will not perform exception view handling. - - Pyramid will prevent the same tween factory from being added to the - tween chain more than once using configuration conflict detection. - If you wish to add the same tween factory more than once in a - configuration, you should either: a) use a tween factory that is an - instance rather than a function or class, b) use a function or - class as a tween factory with the same logic as the other tween - factory it conflicts with but with a different ``__name__`` - attribute or c) call config.commit() between calls to ``add_tween``. - - A user can get a representation of both the implicit tween ordering - (the ordering specified by calls to ``add_tween``) and the explicit - request handler ordering (specified by the ``pyramid.tweens`` - setting) orderings using the ``paster ptweens`` command. Handler - factories which are functions or classes will show up as a standard - Python dotted name in the ``ptweens`` output. Tween factories - which are *instances* will show their module and class name; the - Python object id of the instance will be appended. + For more information, see :ref:`registering_tweens`. .. note:: This feature is new as of Pyramid 1.1.1. """ @@ -1032,7 +946,7 @@ class Configurator(object): registry = self.registry handler_manager = registry.getUtility(ITweens) handler_manager.add(name, tween_factory, explicit) - self.action(('tween', name), register) + self.action(('tween', name, explicit), register) @action_method def add_subscriber(self, subscriber, iface=None): diff --git a/pyramid/router.py b/pyramid/router.py index dcf828377..5faafb4bb 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,10 +1,7 @@ -import sys - from zope.interface import implements from zope.interface import providedBy from pyramid.interfaces import IDebugLogger -from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -24,6 +21,7 @@ from pyramid.request import Request from pyramid.threadlocal import manager from pyramid.traversal import DefaultRootFactory from pyramid.traversal import ResourceTreeTraverser +from pyramid.tweens import excview_tween_factory class Router(object): implements(IRouter) @@ -190,37 +188,3 @@ class Router(object): finally: manager.pop() -def excview_tween_factory(handler, registry): - has_listeners = registry.has_listeners - adapters = registry.adapters - notify = registry.notify - - def exception_view_handler(request): - attrs = request.__dict__ - try: - request, response = handler(request) - except Exception, exc: - # WARNING: do not assign the result of sys.exc_info() to a - # local var here, doing so will cause a leak - attrs['exc_info'] = sys.exc_info() - attrs['exception'] = exc - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] - request_iface = attrs['request_iface'] - provides = providedBy(exc) - for_ = (IExceptionViewClassifier, request_iface.combined, provides) - view_callable = adapters.lookup(for_, IView, default=None) - if view_callable is None: - raise - response = view_callable(exc, request) - has_listeners and notify(NewResponse(request, response)) - finally: - attrs['exc_info'] = None - - return request, response - - return exception_view_handler - diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 8b11bb22a..d73fd7f7d 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -630,7 +630,7 @@ pyramid.tests.test_config.dummy_include2""", def test_add_tweens_names_distinct(self): from pyramid.interfaces import ITweens - from pyramid.router import excview_tween_factory + from pyramid.tweens import excview_tween_factory def factory1(handler, registry): return handler def factory2(handler, registry): return handler config = self._makeOne() @@ -640,13 +640,13 @@ pyramid.tests.test_config.dummy_include2""", tweens = config.registry.queryUtility(ITweens) self.assertEqual( tweens.implicit, - [('pyramid.router.excview_tween_factory', excview_tween_factory), + [('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.factory1', factory1), ('pyramid.tests.test_config.factory2', factory2)]) def test_add_tween_dottedname(self): from pyramid.interfaces import ITweens - from pyramid.router import excview_tween_factory + from pyramid.tweens import excview_tween_factory config = self._makeOne() config.add_tween('pyramid.tests.test_config.dummy_tween_factory') config.commit() @@ -654,14 +654,14 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual( tweens.implicit, [ - ('pyramid.router.excview_tween_factory', excview_tween_factory), + ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.dummy_tween_factory', dummy_tween_factory) ]) def test_add_tween_instance(self): from pyramid.interfaces import ITweens - from pyramid.router import excview_tween_factory + from pyramid.tweens import excview_tween_factory class ATween(object): pass atween = ATween() config = self._makeOne() @@ -671,7 +671,7 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(len(tweens.implicit), 2) self.assertEqual( tweens.implicit[0], - ('pyramid.router.excview_tween_factory', excview_tween_factory)) + ('pyramid.tweens.excview_tween_factory', excview_tween_factory)) self.assertTrue( tweens.implicit[1][0].startswith('pyramid.tests.test_config.ATween.')) self.assertEqual(tweens.implicit[1][1], atween) diff --git a/pyramid/tweens.py b/pyramid/tweens.py new file mode 100644 index 000000000..209768198 --- /dev/null +++ b/pyramid/tweens.py @@ -0,0 +1,45 @@ +import sys +from pyramid.interfaces import IExceptionViewClassifier +from pyramid.interfaces import IView + +from pyramid.events import NewResponse +from zope.interface import providedBy + +def excview_tween_factory(handler, registry): + """ A :term:`tween` factory which produces a tween that catches an + exception raised by downstream tweens (or the main Pyramid request + handler) and, if possible, converts it into a Response using an + :term:`exception view`.""" + has_listeners = registry.has_listeners + adapters = registry.adapters + notify = registry.notify + + def excview_tween(request): + attrs = request.__dict__ + try: + request, response = handler(request) + except Exception, exc: + # WARNING: do not assign the result of sys.exc_info() to a + # local var here, doing so will cause a leak + attrs['exc_info'] = sys.exc_info() + attrs['exception'] = exc + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] + request_iface = attrs['request_iface'] + provides = providedBy(exc) + for_ = (IExceptionViewClassifier, request_iface.combined, provides) + view_callable = adapters.lookup(for_, IView, default=None) + if view_callable is None: + raise + response = view_callable(exc, request) + has_listeners and notify(NewResponse(request, response)) + finally: + attrs['exc_info'] = None + + return request, response + + return excview_tween + -- cgit v1.2.3 From fc38f448d9b35d18f0b7d687efc659e1a2b8ec7c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 19:10:42 -0400 Subject: write tests for ptweens --- pyramid/tests/test_paster.py | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 3cf249c5c..58ed73d2c 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -824,6 +824,79 @@ class TestBootstrap(unittest.TestCase): self.assertEqual(result['root'], self.root) self.assert_('closer' in result) +class TestPTweensCommand(unittest.TestCase): + def _getTargetClass(self): + from pyramid.paster import PTweensCommand + return PTweensCommand + + def _makeOne(self): + cmd = self._getTargetClass()('ptweens') + cmd.bootstrap = (DummyBootstrap(),) + cmd.args = ('/foo/bar/myapp.ini#myapp',) + return cmd + + def test_command_no_tweens(self): + command = self._makeOne() + command._get_tweens = lambda *arg: None + L = [] + command.out = L.append + result = command.command() + self.assertEqual(result, None) + self.assertEqual(L, []) + + def test_command_implicit_tweens_only(self): + command = self._makeOne() + tweens = DummyTweens([('name', 'item')], None) + command._get_tweens = lambda *arg: tweens + L = [] + command.out = L.append + result = command.command() + self.assertEqual(result, None) + self.assertEqual( + L, + ['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)', + '', + 'Position Name ', + '-------- ---- ', + '0 name ', + '']) + + def test_command_implicit_and_explicit_tweens(self): + command = self._makeOne() + tweens = DummyTweens([('name', 'item')], [('name2', 'item2')]) + command._get_tweens = lambda *arg: tweens + L = [] + command.out = L.append + result = command.command() + self.assertEqual(result, None) + self.assertEqual( + L, + ['"pyramid.tweens" config value set (explicitly ordered tweens used)', + '', + 'Explicit Tween Chain (used)', + '', + 'Position Name ', + '-------- ---- ', + '0 name2 ', + '', + 'Implicit Tween Chain (not used)', + '', + 'Position Name ', + '-------- ---- ', + '0 name ', + '' + ]) + + def test__get_tweens(self): + command = self._makeOne() + registry = DummyRegistry() + self.assertEqual(command._get_tweens(registry), None) + +class DummyTweens(object): + def __init__(self, implicit, explicit): + self.implicit = implicit + self.explicit = explicit + class Dummy: pass -- cgit v1.2.3 From 6b2a6210dcda54d81e6e827aed74379f579a2810 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 19:41:40 -0400 Subject: improve changes docs --- CHANGES.txt | 86 +++++++++++++++++++++---------------------------------------- 1 file changed, 29 insertions(+), 57 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c94ecb800..b6f33715e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,63 +12,19 @@ Features ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. -- New configurator directive: - ``pyramid.config.Configurator.add_request_handler``. This directive adds - a request handler factory. - - A request handler factory is used to wrap the Pyramid router's primary - request handling function. This is a feature may be used by framework - extensions, to provide, for example, view timing support and as a - convenient place to hang bookkeeping code that examines exceptions before - they are returned to the server. - - A request handler factory (passed as ``handler_factory``) must be a - callable which accepts two arguments: ``handler`` and ``registry``. - ``handler`` will be the request handler being wrapped. ``registry`` will - be the Pyramid application registry represented by this Configurator. A - request handler factory must return a request handler when it is called. - - A request handler accepts a request object and returns a response object. - - Here's an example of creating both a handler factory and a handler, and - registering the handler factory: - - .. code-block:: python - - import time - - def timing_handler_factory(handler, registry): - if registry.settings['do_timing']: - # if timing support is enabled, return a wrapper - def timing_handler(request): - start = time.time() - try: - response = handler(request) - finally: - end = time.time() - print: 'The request took %s seconds' % (end - start) - return response - return timing_handler - # if timing support is not enabled, return the original handler - return handler - - config.add_request_handler(timing_handler_factory, 'timing') - - The ``request`` argument to the handler will be the request created by - Pyramid's router when it receives a WSGI request. - - If more than one request handler factory is registered into a single - configuration, the request handlers will be chained together. The first - request handler factory added (in code execution order) will be called with - the default Pyramid request handler, the second handler factory added will - be called with the result of the first handler factory, ad infinitum. The - Pyramid router will use the outermost wrapper in this chain (which is a bit - like a WSGI middleware "pipeline") as its handler function. - - The ``name`` argument to this function is required. The name is used as a - key for conflict detection. No two request handler factories may share the - same name in the same configuration (unless automatic_conflict_resolution - is able to resolve the conflict or this is an autocommitting configurator). +- New configurator directive: ``pyramid.config.Configurator.add_tween``. + This directive adds a "tween". A "tween" is used to wrap the Pyramid + router's primary request handling function. This is a feature may be used + by Pyramid framework extensions, to provide, for example, view timing + support and as a convenient place to hang bookkeeping code. + + Tweens are further described in the narrative docs section in the Hooks + chapter, named "Registering Tweens". + +- New paster command ``paster ptweens``, which prints the current "tween" + configuration for an application. See the section entitled "Displaying + Tweens" in the Command-Line Pyramid chapter of the narrative documentation + for more info. - The Pyramid debug logger now uses the standard logging configuration (usually set up by Paste as part of startup). This means that output from @@ -80,6 +36,12 @@ Features will be ``None`` until an exception is caught by the Pyramid router, after which it will be the result of ``sys.exc_info()``. +Internal +-------- + +- The Pyramid "exception view" machinery is now implemented as a "tween" + (``pyramid.tweens.excview_tween_factory``). + Deprecations ------------ @@ -97,6 +59,16 @@ Backwards Incompatibilities that string is considered to be the name of a global Python logger rather than a dotted name to an instance of a logger. +Documentation +------------- + +- Added a new module to the API docs: ``pyramid.tweens``. + +- Added a "Registering Tweens" section to the "Hooks" narrative chapter. + +- Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative + chapter. + 1.1 (2011-07-22) ================ -- cgit v1.2.3 From 479db008942048e29041e7225b8f60b17e843f07 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 20:00:07 -0400 Subject: cope with the realities of trying to initialize a utility in setup_registry; it's not always called --- pyramid/config.py | 60 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index ebbea35ea..126111269 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -79,6 +79,7 @@ from pyramid.threadlocal import manager from pyramid.traversal import DefaultRootFactory from pyramid.traversal import find_interface from pyramid.traversal import traversal_path +from pyramid.tweens import excview_tween_factory from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver from pyramid.util import WeakOrderedSet @@ -716,14 +717,13 @@ class Configurator(object): policies, renderers, a debug logger, a locale negotiator, and various other settings using the configurator's current registry, as per the descriptions in the Configurator constructor.""" + tweens = [] + includes = [] if settings: - includes = settings.get('pyramid.include', '') - includes = [x.strip() for x in includes.splitlines()] - tweens = settings.get('pyramid.tweens','') - tweens = [x.strip() for x in tweens.splitlines()] - else: - includes = [] - tweens = [] + includes = [x.strip() for x in + settings.get('pyramid.include', '').splitlines()] + tweens = [x.strip() for x in + settings.get('pyramid.tweens','').splitlines()] registry = self.registry self._fix_registry() self._set_settings(settings) @@ -734,10 +734,6 @@ class Configurator(object): from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) # add a handler manager - tweenreg = Tweens() - registry.registerUtility(tweenreg, ITweens) - self._add_tween('pyramid.router.excview_tween_factory', explicit=False) - for factory in tweens: self._add_tween(factory, explicit=True) @@ -928,24 +924,18 @@ class Configurator(object): def _add_tween(self, tween_factory, explicit): tween_factory = self.maybe_dotted(tween_factory) - if (hasattr(tween_factory, '__name__') and - hasattr(tween_factory, '__module__')): - # function or class - name = '.'.join([tween_factory.__module__, - tween_factory.__name__]) - elif hasattr(tween_factory, '__module__'): - # instance - name = '.'.join([tween_factory.__module__, - tween_factory.__class__.__name__, - str(id(tween_factory))]) - else: - raise ConfigurationError( - 'A tween factory must be a class, an instance, or a function; ' - '%s is not a suitable tween factory' % tween_factory) + name = tween_factory_name(tween_factory) def register(): registry = self.registry - handler_manager = registry.getUtility(ITweens) - handler_manager.add(name, tween_factory, explicit) + tweens = registry.queryUtility(ITweens) + if tweens is None: + tweens = Tweens() + registry.registerUtility(tweens, ITweens) + tweens.add( + tween_factory_name(excview_tween_factory), + excview_tween_factory, + explicit=False) + tweens.add(name, tween_factory, explicit) self.action(('tween', name, explicit), register) @action_method @@ -3403,3 +3393,19 @@ class Tweens(object): handler = factory(handler, registry) return handler +def tween_factory_name(factory): + if (hasattr(factory, '__name__') and + hasattr(factory, '__module__')): + # function or class + name = '.'.join([factory.__module__, + factory.__name__]) + elif hasattr(factory, '__module__'): + # instance + name = '.'.join([factory.__module__, + factory.__class__.__name__, + str(id(factory))]) + else: + raise ConfigurationError( + 'A tween factory must be a class, an instance, or a function; ' + '%s is not a suitable tween factory' % factory) + return name -- cgit v1.2.3 From 8d1533d95ceebbef641a220236b77d9b3931cc31 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 20:22:43 -0400 Subject: change return value of tween to just response --- docs/narr/hooks.rst | 7 +++---- pyramid/router.py | 4 ++-- pyramid/tweens.py | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 7290876e6..c6438dfdf 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -855,8 +855,7 @@ will be the Pyramid :term:`application registry` represented by this Configurator. A tween factory must return a tween when it is called. A tween is a callable which accepts a :term:`request` object and returns a -two-tuple consisting of a :term:`request` object and a :term:`response` -object. +two-tuple a :term:`response` object. Once you've created a tween factory, you can register it using the :meth:`pyramid.config.Configurator.add_tween` method. @@ -878,12 +877,12 @@ Here's an example creating a tween factory and registering it: def timing_tween(request): start = time.time() try: - request, response = handler(request) + response = handler(request) finally: end = time.time() log.debug('The request took %s seconds' % (end - start)) - return request, response + return response return timing_tween # if timing support is not enabled, return the original # handler diff --git a/pyramid/router.py b/pyramid/router.py index 5faafb4bb..79fd7992b 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -162,7 +162,7 @@ class Router(object): if request.response_callbacks: request._process_response_callbacks(response) - return request, response + return response finally: if request is not None and request.finished_callbacks: @@ -183,7 +183,7 @@ class Router(object): manager.push(threadlocals) try: request.registry = registry - request, response = self.handle_request(request) + response = self.handle_request(request) return response(request.environ, start_response) finally: manager.pop() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 209768198..f7673a738 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -17,7 +17,7 @@ def excview_tween_factory(handler, registry): def excview_tween(request): attrs = request.__dict__ try: - request, response = handler(request) + response = handler(request) except Exception, exc: # WARNING: do not assign the result of sys.exc_info() to a # local var here, doing so will cause a leak @@ -39,7 +39,7 @@ def excview_tween_factory(handler, registry): finally: attrs['exc_info'] = None - return request, response + return response return excview_tween -- cgit v1.2.3 From ca55f9754876c572fe638553aebc90120f12d6fe Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 20:29:57 -0400 Subject: readd bw compat for toolbar (temporary) --- pyramid/config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyramid/config.py b/pyramid/config.py index 126111269..a12df8ef7 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -937,6 +937,11 @@ class Configurator(object): explicit=False) tweens.add(name, tween_factory, explicit) self.action(('tween', name, explicit), register) + + @action_method + def add_request_handler(self, factory, name): # pragma: no cover + # XXX bw compat for debugtoolbar + return self._add_tween(factory, explicit=False) @action_method def add_subscriber(self, subscriber, iface=None): -- cgit v1.2.3 From 18a99ae15e7f36cd21da6e2d2e70d61d1733bf30 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 6 Aug 2011 20:35:34 -0400 Subject: docs rendering --- docs/narr/hooks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index c6438dfdf..889e8d6d8 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -863,7 +863,7 @@ Once you've created a tween factory, you can register it using the Here's an example creating a tween factory and registering it: .. code-block:: python - :lineno: + :linenos: import time from pyramid.settings import asbool -- cgit v1.2.3 From 8517d44072ee79535097ccfda25764f72ec45f81 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 7 Aug 2011 11:25:53 -0400 Subject: first cut at topological sorting of tweens given hints provided by the framework extender --- pyramid/config.py | 62 ++++----------- pyramid/paster.py | 14 ++-- pyramid/tests/test_config.py | 54 +++---------- pyramid/tests/test_paster.py | 31 +++++--- pyramid/tests/test_router.py | 4 +- pyramid/tests/test_tweens.py | 180 +++++++++++++++++++++++++++++++++++++++++++ pyramid/tweens.py | 147 +++++++++++++++++++++++++++++++++++ 7 files changed, 381 insertions(+), 111 deletions(-) create mode 100644 pyramid/tests/test_tweens.py diff --git a/pyramid/config.py b/pyramid/config.py index a12df8ef7..6ae9a70ce 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -80,6 +80,9 @@ from pyramid.traversal import DefaultRootFactory from pyramid.traversal import find_interface from pyramid.traversal import traversal_path from pyramid.tweens import excview_tween_factory +from pyramid.tweens import Tweens +from pyramid.tweens import tween_factory_name +from pyramid.tweens import MAIN from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver from pyramid.util import WeakOrderedSet @@ -733,9 +736,6 @@ class Configurator(object): # cope with WebOb exc objects not decoratored with IExceptionResponse from webob.exc import WSGIHTTPException as WebobWSGIHTTPException registry.registerSelfAdapter((WebobResponse,), IResponse) - # add a handler manager - for factory in tweens: - self._add_tween(factory, explicit=True) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) @@ -784,6 +784,8 @@ class Configurator(object): self.commit() for inc in includes: self.include(inc) + for factory in tweens: + self._add_tween(factory, explicit=True) def hook_zca(self): """ Call :func:`zope.component.getSiteManager.sethook` with @@ -903,7 +905,7 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_tween(self, tween_factory): + def add_tween(self, tween_factory, below=None, atop=None): """ Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit of code that sits between the Pyramid router's main request handling @@ -920,9 +922,10 @@ class Configurator(object): .. note:: This feature is new as of Pyramid 1.1.1. """ - return self._add_tween(tween_factory, explicit=False) + return self._add_tween(tween_factory, below=below, atop=atop, + explicit=False) - def _add_tween(self, tween_factory, explicit): + def _add_tween(self, tween_factory, below=None, atop=None, explicit=False): tween_factory = self.maybe_dotted(tween_factory) name = tween_factory_name(tween_factory) def register(): @@ -931,11 +934,12 @@ class Configurator(object): if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) - tweens.add( - tween_factory_name(excview_tween_factory), - excview_tween_factory, - explicit=False) - tweens.add(name, tween_factory, explicit) + tweens.add_implicit(tween_factory_name(excview_tween_factory), + excview_tween_factory, below=MAIN) + if explicit: + tweens.add_explicit(name, tween_factory) + else: + tweens.add_implicit(name, tween_factory, below=below, atop=atop) self.action(('tween', name, explicit), register) @action_method @@ -3378,39 +3382,3 @@ def isexception(o): global_registries = WeakOrderedSet() -class Tweens(object): - implements(ITweens) - def __init__(self): - self.explicit = [] - self.implicit = [] - - def add(self, name, factory, explicit=False): - if explicit: - self.explicit.append((name, factory)) - else: - self.implicit.append((name, factory)) - - def __call__(self, handler, registry): - factories = self.implicit - if self.explicit: - factories = self.explicit - for name, factory in factories: - handler = factory(handler, registry) - return handler - -def tween_factory_name(factory): - if (hasattr(factory, '__name__') and - hasattr(factory, '__module__')): - # function or class - name = '.'.join([factory.__module__, - factory.__name__]) - elif hasattr(factory, '__module__'): - # instance - name = '.'.join([factory.__module__, - factory.__class__.__name__, - str(id(factory))]) - else: - raise ConfigurationError( - 'A tween factory must be a class, an instance, or a function; ' - '%s is not a suitable tween factory' % factory) - return name diff --git a/pyramid/paster.py b/pyramid/paster.py index 54f5a51a6..e7ec8fb93 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -574,27 +574,31 @@ class PTweensCommand(PCommand): registry = env['registry'] tweens = self._get_tweens(registry) if tweens is not None: + implicit = tweens.implicit() + explicit = tweens.explicit ordering = [] - if tweens.explicit: + if explicit: self.out('"pyramid.tweens" config value set ' '(explicitly ordered tweens used)') self.out('') - ordering.append((tweens.explicit, + ordering.append((explicit, 'Explicit Tween Chain (used)')) - ordering.append((tweens.implicit, + ordering.append((implicit, 'Implicit Tween Chain (not used)')) else: self.out('"pyramid.tweens" config value NOT set ' '(implicitly ordered tweens used)') self.out('') - ordering.append((tweens.implicit, '')) + ordering.append((implicit, '')) for L, title in ordering: if title: self.out(title) self.out('') - fmt = '%-8s %-30s' + fmt = '%-10s %-30s' self.out(fmt % ('Position', 'Name')) self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) + self.out(fmt % ('(implied)', 'main')) for pos, (name, item) in enumerate(L): self.out(fmt % (pos, name)) + self.out(fmt % ('(implied)', 'ingress')) self.out('') diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index d73fd7f7d..598362ccb 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -592,6 +592,7 @@ pyramid.tests.test_config.dummy_include2""", 'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory' } config.setup_registry(settings=settings) + config.commit() tweens = config.registry.getUtility(ITweens) self.assertEqual(tweens.explicit, [('pyramid.tests.test_config.dummy_tween_factory', @@ -639,7 +640,7 @@ pyramid.tests.test_config.dummy_include2""", config.commit() tweens = config.registry.queryUtility(ITweens) self.assertEqual( - tweens.implicit, + tweens.implicit(), [('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.factory1', factory1), ('pyramid.tests.test_config.factory2', factory2)]) @@ -652,7 +653,7 @@ pyramid.tests.test_config.dummy_include2""", config.commit() tweens = config.registry.queryUtility(ITweens) self.assertEqual( - tweens.implicit, + tweens.implicit(), [ ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.dummy_tween_factory', @@ -668,13 +669,15 @@ pyramid.tests.test_config.dummy_include2""", config.add_tween(atween) config.commit() tweens = config.registry.queryUtility(ITweens) - self.assertEqual(len(tweens.implicit), 2) + implicit = tweens.implicit() + self.assertEqual(len(implicit), 2) self.assertEqual( - tweens.implicit[0], + implicit[0], ('pyramid.tweens.excview_tween_factory', excview_tween_factory)) self.assertTrue( - tweens.implicit[1][0].startswith('pyramid.tests.test_config.ATween.')) - self.assertEqual(tweens.implicit[1][1], atween) + implicit[1][0].startswith( + 'pyramid.tests.test_config.ATween.')) + self.assertEqual(implicit[1][1], atween) def test_add_tween_unsuitable(self): from pyramid.exceptions import ConfigurationError @@ -5511,45 +5514,6 @@ class Test_isexception(unittest.TestCase): pass self.assertEqual(self._callFUT(ISubException), True) -class TestTweens(unittest.TestCase): - def _makeOne(self): - from pyramid.config import Tweens - return Tweens() - - def test_add_explicit(self): - tweens = self._makeOne() - tweens.add('name', 'factory', explicit=True) - self.assertEqual(tweens.explicit, [('name', 'factory')]) - tweens.add('name2', 'factory2', explicit=True) - self.assertEqual(tweens.explicit, [('name', 'factory'), - ('name2', 'factory2')]) - - def test_add_implicit(self): - tweens = self._makeOne() - tweens.add('name', 'factory', explicit=False) - self.assertEqual(tweens.implicit, [('name', 'factory')]) - tweens.add('name2', 'factory2', explicit=False) - self.assertEqual(tweens.implicit, [('name', 'factory'), - ('name2', 'factory2')]) - - def test___call___explicit(self): - tweens = self._makeOne() - def factory1(handler, registry): - return handler - def factory2(handler, registry): - return '123' - tweens.explicit = [('name', factory1), ('name', factory2)] - self.assertEqual(tweens(None, None), '123') - - def test___call___implicit(self): - tweens = self._makeOne() - def factory1(handler, registry): - return handler - def factory2(handler, registry): - return '123' - tweens.implicit = [('name', factory1), ('name', factory2)] - self.assertEqual(tweens(None, None), '123') - class DummyRequest: subpath = () matchdict = None diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 58ed73d2c..be7aa386e 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -856,9 +856,11 @@ class TestPTweensCommand(unittest.TestCase): L, ['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)', '', - 'Position Name ', - '-------- ---- ', - '0 name ', + 'Position Name ', + '-------- ---- ', + '(implied) main ', + '0 name ', + '(implied) ingress ', '']) def test_command_implicit_and_explicit_tweens(self): @@ -875,17 +877,20 @@ class TestPTweensCommand(unittest.TestCase): '', 'Explicit Tween Chain (used)', '', - 'Position Name ', - '-------- ---- ', - '0 name2 ', + 'Position Name ', + '-------- ---- ', + '(implied) main ', + '0 name2 ', + '(implied) ingress ', '', 'Implicit Tween Chain (not used)', '', - 'Position Name ', - '-------- ---- ', - '0 name ', - '' - ]) + 'Position Name ', + '-------- ---- ', + '(implied) main ', + '0 name ', + '(implied) ingress ', + '']) def test__get_tweens(self): command = self._makeOne() @@ -894,8 +899,10 @@ class TestPTweensCommand(unittest.TestCase): class DummyTweens(object): def __init__(self, implicit, explicit): - self.implicit = implicit + self._implicit = implicit self.explicit = explicit + def implicit(self): + return self._implicit class Dummy: pass diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 6b0354468..19134813f 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -159,8 +159,8 @@ class TestRouter(unittest.TestCase): wrapper.name = 'two' wrapper.child = handler return wrapper - tweens.add('one', tween_factory1) - tweens.add('two', tween_factory2) + tweens.add_implicit('one', tween_factory1) + tweens.add_implicit('two', tween_factory2) router = self._makeOne() self.assertEqual(router.handle_request.name, 'two') self.assertEqual(router.handle_request.child.name, 'one') diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py new file mode 100644 index 000000000..7d5ae11b2 --- /dev/null +++ b/pyramid/tests/test_tweens.py @@ -0,0 +1,180 @@ +import unittest + +class TestTweens(unittest.TestCase): + def _makeOne(self): + from pyramid.config import Tweens + return Tweens() + + def test_add_explicit(self): + tweens = self._makeOne() + tweens.add_explicit('name', 'factory') + self.assertEqual(tweens.explicit, [('name', 'factory')]) + tweens.add_explicit('name2', 'factory2') + self.assertEqual(tweens.explicit, [('name', 'factory'), + ('name2', 'factory2')]) + + def test_add_implicit(self): + from pyramid.tweens import INGRESS + tweens = self._makeOne() + tweens.add_implicit('name', 'factory') + self.assertEqual(tweens.implicit_names, ['name']) + self.assertEqual(tweens.implicit_factories, + {'name':'factory'}) + self.assertEqual(tweens.implicit_ingress_names, ['name']) + self.assertEqual(tweens.implicit_order, [('name', INGRESS)]) + tweens.add_implicit('name2', 'factory2') + self.assertEqual(tweens.implicit_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_factories, + {'name':'factory', 'name2':'factory2'}) + self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_order, + [('name', INGRESS), ('name2', INGRESS)]) + tweens.add_implicit('name3', 'factory3', below='name2') + self.assertEqual(tweens.implicit_names, ['name', 'name2', 'name3']) + self.assertEqual(tweens.implicit_factories, + {'name':'factory', 'name2':'factory2', + 'name3':'factory3'}) + self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_order, + [('name', INGRESS), ('name2', INGRESS), + ('name2', 'name3')]) + + def test___call___explicit(self): + tweens = self._makeOne() + def factory1(handler, registry): + return handler + def factory2(handler, registry): + return '123' + tweens.explicit = [('name', factory1), ('name', factory2)] + self.assertEqual(tweens(None, None), '123') + + def test___call___implicit(self): + tweens = self._makeOne() + def factory1(handler, registry): + return handler + def factory2(handler, registry): + return '123' + tweens.implicit_names = ['name', 'name2'] + tweens.implicit_factories = {'name':factory1, 'name2':factory2} + self.assertEqual(tweens(None, None), '123') + + def test_implicit_ordering_1(self): + tweens = self._makeOne() + tweens.add_implicit('name1', 'factory1') + tweens.add_implicit('name2', 'factory2') + self.assertEqual(tweens.implicit(), [('name1', 'factory1'), + ('name2', 'factory2')]) + + def test_implicit_ordering_2(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + tweens.add_implicit('name1', 'factory1') + tweens.add_implicit('name2', 'factory2', below=MAIN) + self.assertEqual(tweens.implicit(), + [('name2', 'factory2'), ('name1', 'factory1')]) + + def test_implicit_ordering_3(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('auth', 'auth_factory', atop='browserid') + add('dbt', 'dbt_factory') + add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('browserid', 'browserid_factory') + add('txnmgr', 'txnmgr_factory', atop='exceptionview') + add('exceptionview', 'excview_factory', below=MAIN) + self.assertEqual(tweens.implicit(), + [('txnmgr', 'txnmgr_factory'), + ('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('auth', 'auth_factory'), + ('browserid', 'browserid_factory'), + ('dbt', 'dbt_factory')]) + + def test_implicit_ordering_4(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', below=MAIN) + add('auth', 'auth_factory', atop='browserid') + add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('browserid', 'browserid_factory') + add('txnmgr', 'txnmgr_factory', atop='exceptionview') + add('dbt', 'dbt_factory') + self.assertEqual(tweens.implicit(), + [('txnmgr', 'txnmgr_factory'), + ('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('auth', 'auth_factory'), + ('browserid', 'browserid_factory'), + ('dbt', 'dbt_factory')]) + + def test_implicit_ordering_missing_partial(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', below=MAIN) + add('auth', 'auth_factory', atop='browserid') + add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('browserid', 'browserid_factory') + add('dbt', 'dbt_factory') + self.assertEqual(tweens.implicit(), + [('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('auth', 'auth_factory'), + ('browserid', 'browserid_factory'), + ('dbt', 'dbt_factory')]) + + def test_implicit_ordering_missing_partial2(self): + tweens = self._makeOne() + add = tweens.add_implicit + add('dbt', 'dbt_factory') + add('auth', 'auth_factory', atop='browserid') + add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('browserid', 'browserid_factory') + self.assertEqual(tweens.implicit(), + [('dbt', 'dbt_factory'), + ('auth', 'auth_factory'), + ('browserid', 'browserid_factory'), + ('retry', 'retry_factory')]) + + def test_implicit_ordering_missing_partial3(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', below=MAIN) + add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('browserid', 'browserid_factory') + self.assertEqual(tweens.implicit(), + [('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('browserid', 'browserid_factory')]) + + def test_implicit_ordering_conflict_direct(self): + from pyramid.tweens import CyclicDependencyError + tweens = self._makeOne() + add = tweens.add_implicit + add('browserid', 'browserid_factory') + add('auth', 'auth_factory', atop='browserid', below='browserid') + self.assertRaises(CyclicDependencyError, tweens.implicit) + + def test_implicit_ordering_conflict_indirect(self): + from pyramid.tweens import CyclicDependencyError + tweens = self._makeOne() + add = tweens.add_implicit + add('browserid', 'browserid_factory') + add('auth', 'auth_factory', atop='browserid') + add('dbt', 'dbt_factory', below='browserid', atop='auth') + self.assertRaises(CyclicDependencyError, tweens.implicit) + +class TestCyclicDependencyError(unittest.TestCase): + def _makeOne(self, cycles): + from pyramid.tweens import CyclicDependencyError + return CyclicDependencyError(cycles) + + def test___str__(self): + exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) + result = str(exc) + self.assertEqual(result, + "'a' sorts atop ['c', 'd']; 'c' sorts atop ['a']") + diff --git a/pyramid/tweens.py b/pyramid/tweens.py index f7673a738..93a564cfc 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -1,9 +1,12 @@ import sys +from pyramid.exceptions import ConfigurationError from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IView +from pyramid.interfaces import ITweens from pyramid.events import NewResponse from zope.interface import providedBy +from zope.interface import implements def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an @@ -43,3 +46,147 @@ def excview_tween_factory(handler, registry): return excview_tween +class CyclicDependencyError(Exception): + def __init__(self, cycles): + self.cycles = cycles + + def __str__(self): + L = [] + cycles = self.cycles + for cycle in cycles: + dependent = cycle + dependees = cycles[cycle] + L.append('%r sorts atop %r' % (dependent, dependees)) + msg = '; '.join(L) + return msg + +MAIN = 'main' +INGRESS = 'ingress' + +class Tweens(object): + implements(ITweens) + def __init__(self): + self.explicit = [] + self.implicit_names = [] + self.implicit_factories = {} + self.implicit_order = [] + self.implicit_ingress_names = [] + + def add_explicit(self, name, factory): + self.explicit.append((name, factory)) + + def add_implicit(self, name, factory, below=None, atop=None): + if below is None and atop is None: + atop = 'ingress' + self.implicit_ingress_names.append(name) + if below is not None: + order = (below, name) + self.implicit_order.append(order) + if atop is not None: + order = (name, atop) + self.implicit_order.append(order) + self.implicit_names.append(name) + self.implicit_factories[name] = factory + + def implicit(self): + roots = [] + graph = {} + + def add_node(graph, node): + if not graph.has_key(node): + roots.append(node) + graph[node] = [0] # 0 = number of arcs coming into this node + + def add_arc(graph, fromnode, tonode): + graph[fromnode].append(tonode) + graph[tonode][0] += 1 + if tonode in roots: + roots.remove(tonode) + + names = [MAIN, INGRESS] + self.implicit_names + + orders = {} + + for pos, (first, second) in enumerate(self.implicit_order): + has_first = first in names + has_second = second in names + if (not has_first) or (not has_second): + self.implicit_order[pos] = None, None # FFF + else: + orders[first] = orders[second] = True + + for v in names: + # any name that doesn't have an ordering after we detect all + # nodes with orders should get an ordering relative to INGRESS, + # as if it were added with no below or atop + if (not v in orders) and (v not in (INGRESS, MAIN)): + self.implicit_order.append((v, INGRESS)) + self.implicit_ingress_names.append(v) + add_node(graph, v) + + for a, b in self.implicit_order: + if a is not None and b is not None: # see FFF above + add_arc(graph, a, b) + + def sortroots(name): + # sort roots so that roots (and their children) that depend only on + # the ingress sort nearer the end (nearer the ingress) + if name in self.implicit_ingress_names: + return 1 + children = graph[name][1:] + for child in children: + if sortroots(child) == 1: + return 1 + return -1 + + roots.sort(key=sortroots) + + sorted_tweens = [] + + while roots: + root = roots.pop(0) + sorted_tweens.append(root) + children = graph[root][1:] + for child in children: + arcs = graph[child][0] + arcs -= 1 + graph[child][0] = arcs + if arcs == 0: + roots.insert(0, child) + del graph[root] + + if graph: + # loop in input + cycledeps = {} + for k, v in graph.items(): + cycledeps[k] = v[1:] + raise CyclicDependencyError(cycledeps) + + return [ (name, self.implicit_factories[name]) for name in + sorted_tweens if name not in (MAIN, INGRESS) ] + + def __call__(self, handler, registry): + if self.explicit: + factories = self.explicit + else: + factories = self.implicit() + for name, factory in factories: + handler = factory(handler, registry) + return handler + +def tween_factory_name(factory): + if (hasattr(factory, '__name__') and + hasattr(factory, '__module__')): + # function or class + name = '.'.join([factory.__module__, + factory.__name__]) + elif hasattr(factory, '__module__'): + # instance + name = '.'.join([factory.__module__, + factory.__class__.__name__, + str(id(factory))]) + else: + raise ConfigurationError( + 'A tween factory must be a class, an instance, or a function; ' + '%s is not a suitable tween factory' % factory) + return name -- cgit v1.2.3 From 3c9845ccf38b9adc9dd1e4c01a6291f12642db1e Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 7 Aug 2011 15:18:22 -0400 Subject: make the excview_tween_factory name importable --- pyramid/tweens.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 93a564cfc..56945b165 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -60,9 +60,6 @@ class CyclicDependencyError(Exception): msg = '; '.join(L) return msg -MAIN = 'main' -INGRESS = 'ingress' - class Tweens(object): implements(ITweens) def __init__(self): @@ -190,3 +187,8 @@ def tween_factory_name(factory): 'A tween factory must be a class, an instance, or a function; ' '%s is not a suitable tween factory' % factory) return name + +MAIN = 'main' +INGRESS = 'ingress' +EXCVIEW = tween_factory_name(excview_tween_factory) + -- cgit v1.2.3 From da13810625ae92f3d76a36dcb88827a18ae4c8f8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 7 Aug 2011 15:33:44 -0400 Subject: prevent both mainhandler and excview tween from needing to send a NewResponse event --- pyramid/router.py | 230 +++++++++++++++++++++++++++--------------------------- pyramid/tweens.py | 5 +- 2 files changed, 116 insertions(+), 119 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 79fd7992b..248ab3b5b 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -39,15 +39,11 @@ class Router(object): self.request_factory = q(IRequestFactory, default=Request) tweens = q(ITweens) if tweens is None: - self.handle_request = excview_tween_factory(self.handle_request, - registry) - else: - self.handle_request = tweens(self.handle_request, registry) - + tweens = excview_tween_factory + self.handle_request = tweens(self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings - if settings is not None: self.debug_notfound = settings['debug_notfound'] self.debug_routematch = settings['debug_routematch'] @@ -56,117 +52,107 @@ class Router(object): attrs = request.__dict__ registry = attrs['registry'] - try: # matches finally: if request is not None - request.request_iface = IRequest - context = None - routes_mapper = self.routes_mapper - debug_routematch = self.debug_routematch - adapters = registry.adapters - has_listeners = registry.has_listeners - notify = registry.notify - logger = self.logger - - has_listeners and notify(NewRequest(request)) - # find the root object - root_factory = self.root_factory - if routes_mapper is not None: - info = routes_mapper(request) - match, route = info['match'], info['route'] - if route is None: - if debug_routematch: - msg = ('no route matched for url %s' % - request.url) - logger and logger.debug(msg) - else: - # TODO: kill off bfg.routes.* environ keys - # when traverser requires request arg, and - # cant cope with environ anymore (they are - # docs-deprecated as of BFG 1.3) - environ = request.environ - environ['bfg.routes.route'] = route - environ['bfg.routes.matchdict'] = match - attrs['matchdict'] = match - attrs['matched_route'] = route - - if debug_routematch: - msg = ( - 'route matched for url %s; ' - 'route_name: %r, ' - 'path_info: %r, ' - 'pattern: %r, ' - 'matchdict: %r, ' - 'predicates: %r' % ( - request.url, - route.name, - request.path_info, - route.pattern, match, - route.predicates) - ) - logger and logger.debug(msg) - - request.request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) - - root_factory = route.factory or self.root_factory - - root = root_factory(request) - attrs['root'] = root - - # find a context - traverser = adapters.queryAdapter(root, ITraverser) - if traverser is None: - traverser = ResourceTreeTraverser(root) - tdict = traverser(request) - - context, view_name, subpath, traversed, vroot, vroot_path = ( - tdict['context'], - tdict['view_name'], - tdict['subpath'], - tdict['traversed'], - tdict['virtual_root'], - tdict['virtual_root_path'] - ) - - attrs.update(tdict) - has_listeners and notify(ContextFound(request)) - - # find a view callable - context_iface = providedBy(context) - view_callable = adapters.lookup( - (IViewClassifier, request.request_iface, context_iface), - IView, name=view_name, default=None) - - # invoke the view callable - if view_callable is None: - if self.debug_notfound: + request.request_iface = IRequest + context = None + routes_mapper = self.routes_mapper + debug_routematch = self.debug_routematch + adapters = registry.adapters + has_listeners = registry.has_listeners + notify = registry.notify + logger = self.logger + + has_listeners and notify(NewRequest(request)) + # find the root object + root_factory = self.root_factory + if routes_mapper is not None: + info = routes_mapper(request) + match, route = info['match'], info['route'] + if route is None: + if debug_routematch: + msg = ('no route matched for url %s' % + request.url) + logger and logger.debug(msg) + else: + # TODO: kill off bfg.routes.* environ keys + # when traverser requires request arg, and + # cant cope with environ anymore (they are + # docs-deprecated as of BFG 1.3) + environ = request.environ + environ['bfg.routes.route'] = route + environ['bfg.routes.matchdict'] = match + attrs['matchdict'] = match + attrs['matched_route'] = route + + if debug_routematch: msg = ( - 'debug_notfound of url %s; path_info: %r, ' - 'context: %r, view_name: %r, subpath: %r, ' - 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, subpath, traversed, root, vroot, - vroot_path) + 'route matched for url %s; ' + 'route_name: %r, ' + 'path_info: %r, ' + 'pattern: %r, ' + 'matchdict: %r, ' + 'predicates: %r' % ( + request.url, + route.name, + request.path_info, + route.pattern, match, + route.predicates) ) logger and logger.debug(msg) - else: - msg = request.path_info - raise HTTPNotFound(msg) - else: - response = view_callable(context, request) - - has_listeners and notify(NewResponse(request, response)) - - if request.response_callbacks: - request._process_response_callbacks(response) - return response + request.request_iface = registry.queryUtility( + IRouteRequest, + name=route.name, + default=IRequest) + + root_factory = route.factory or self.root_factory + + root = root_factory(request) + attrs['root'] = root + + # find a context + traverser = adapters.queryAdapter(root, ITraverser) + if traverser is None: + traverser = ResourceTreeTraverser(root) + tdict = traverser(request) + + context, view_name, subpath, traversed, vroot, vroot_path = ( + tdict['context'], + tdict['view_name'], + tdict['subpath'], + tdict['traversed'], + tdict['virtual_root'], + tdict['virtual_root_path'] + ) + + attrs.update(tdict) + has_listeners and notify(ContextFound(request)) + + # find a view callable + context_iface = providedBy(context) + view_callable = adapters.lookup( + (IViewClassifier, request.request_iface, context_iface), + IView, name=view_name, default=None) + + # invoke the view callable + if view_callable is None: + if self.debug_notfound: + msg = ( + 'debug_notfound of url %s; path_info: %r, ' + 'context: %r, view_name: %r, subpath: %r, ' + 'traversed: %r, root: %r, vroot: %r, ' + 'vroot_path: %r' % ( + request.url, request.path_info, context, + view_name, subpath, traversed, root, vroot, + vroot_path) + ) + logger and logger.debug(msg) + else: + msg = request.path_info + raise HTTPNotFound(msg) + else: + response = view_callable(context, request) - finally: - if request is not None and request.finished_callbacks: - request._process_finished_callbacks() + return response def __call__(self, environ, start_response): """ @@ -177,14 +163,28 @@ class Router(object): return an iterable. """ registry = self.registry + has_listeners = self.registry.has_listeners + notify = self.registry.notify request = self.request_factory(environ) threadlocals = {'registry':registry, 'request':request} manager = self.threadlocal_manager manager.push(threadlocals) + request.registry = registry try: - request.registry = registry - response = self.handle_request(request) - return response(request.environ, start_response) + + try: + response = self.handle_request(request) + has_listeners and notify(NewResponse(request, response)) + + if request.response_callbacks: + request._process_response_callbacks(response) + + return response(request.environ, start_response) + + finally: + if request.finished_callbacks: + request._process_finished_callbacks() + finally: manager.pop() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 56945b165..0039b0e39 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -4,7 +4,6 @@ from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IView from pyramid.interfaces import ITweens -from pyramid.events import NewResponse from zope.interface import providedBy from zope.interface import implements @@ -13,9 +12,7 @@ def excview_tween_factory(handler, registry): exception raised by downstream tweens (or the main Pyramid request handler) and, if possible, converts it into a Response using an :term:`exception view`.""" - has_listeners = registry.has_listeners adapters = registry.adapters - notify = registry.notify def excview_tween(request): attrs = request.__dict__ @@ -38,8 +35,8 @@ def excview_tween_factory(handler, registry): if view_callable is None: raise response = view_callable(exc, request) - has_listeners and notify(NewResponse(request, response)) finally: + # prevent leakage attrs['exc_info'] = None return response -- cgit v1.2.3 From 06b15bf2024b553556ab8a56c185ec7229d20ed8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 7 Aug 2011 15:53:11 -0400 Subject: add support for tween aliases --- pyramid/tests/test_tweens.py | 27 ++++++++++++++++------ pyramid/tweens.py | 54 ++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index 7d5ae11b2..c8c2e591c 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -17,24 +17,25 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import INGRESS tweens = self._makeOne() tweens.add_implicit('name', 'factory') - self.assertEqual(tweens.implicit_names, ['name']) + self.assertEqual(tweens.implicit_alias_names, ['name']) self.assertEqual(tweens.implicit_factories, {'name':'factory'}) - self.assertEqual(tweens.implicit_ingress_names, ['name']) + self.assertEqual(tweens.implicit_ingress_alias_names, ['name']) self.assertEqual(tweens.implicit_order, [('name', INGRESS)]) tweens.add_implicit('name2', 'factory2') - self.assertEqual(tweens.implicit_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_alias_names, ['name', 'name2']) self.assertEqual(tweens.implicit_factories, {'name':'factory', 'name2':'factory2'}) - self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_ingress_alias_names, ['name', 'name2']) self.assertEqual(tweens.implicit_order, [('name', INGRESS), ('name2', INGRESS)]) tweens.add_implicit('name3', 'factory3', below='name2') - self.assertEqual(tweens.implicit_names, ['name', 'name2', 'name3']) + self.assertEqual(tweens.implicit_alias_names, + ['name', 'name2', 'name3']) self.assertEqual(tweens.implicit_factories, {'name':'factory', 'name2':'factory2', 'name3':'factory3'}) - self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2']) + self.assertEqual(tweens.implicit_ingress_alias_names, ['name', 'name2']) self.assertEqual(tweens.implicit_order, [('name', INGRESS), ('name2', INGRESS), ('name2', 'name3')]) @@ -54,7 +55,19 @@ class TestTweens(unittest.TestCase): return handler def factory2(handler, registry): return '123' - tweens.implicit_names = ['name', 'name2'] + tweens.implicit_alias_names = ['name', 'name2'] + tweens.implicit_aliases = {'name':'name', 'name2':'name2'} + tweens.implicit_factories = {'name':factory1, 'name2':factory2} + self.assertEqual(tweens(None, None), '123') + + def test___call___implicit_with_aliasnames_different_than_names(self): + tweens = self._makeOne() + def factory1(handler, registry): + return handler + def factory2(handler, registry): + return '123' + tweens.implicit_alias_names = ['foo1', 'foo2'] + tweens.implicit_aliases = {'foo1':'name', 'foo2':'name2'} tweens.implicit_factories = {'name':factory1, 'name2':factory2} self.assertEqual(tweens(None, None), '123') diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 0039b0e39..ee46ec94a 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -61,26 +61,30 @@ class Tweens(object): implements(ITweens) def __init__(self): self.explicit = [] - self.implicit_names = [] + self.implicit_alias_names = [] self.implicit_factories = {} self.implicit_order = [] - self.implicit_ingress_names = [] + self.implicit_ingress_alias_names = [] + self.implicit_aliases = {} def add_explicit(self, name, factory): self.explicit.append((name, factory)) - def add_implicit(self, name, factory, below=None, atop=None): + def add_implicit(self, name, factory, alias=None, below=None, atop=None): + if alias is None: + alias = name + self.implicit_aliases[alias] = name + self.implicit_alias_names.append(alias) + self.implicit_factories[name] = factory if below is None and atop is None: - atop = 'ingress' - self.implicit_ingress_names.append(name) + atop = INGRESS + self.implicit_ingress_alias_names.append(alias) if below is not None: - order = (below, name) + order = (below, alias) self.implicit_order.append(order) if atop is not None: - order = (name, atop) + order = (alias, atop) self.implicit_order.append(order) - self.implicit_names.append(name) - self.implicit_factories[name] = factory def implicit(self): roots = [] @@ -97,25 +101,25 @@ class Tweens(object): if tonode in roots: roots.remove(tonode) - names = [MAIN, INGRESS] + self.implicit_names + aliases = [MAIN, INGRESS] + self.implicit_alias_names orders = {} for pos, (first, second) in enumerate(self.implicit_order): - has_first = first in names - has_second = second in names + has_first = first in aliases + has_second = second in aliases if (not has_first) or (not has_second): self.implicit_order[pos] = None, None # FFF else: orders[first] = orders[second] = True - for v in names: - # any name that doesn't have an ordering after we detect all + for v in aliases: + # any alias that doesn't have an ordering after we detect all # nodes with orders should get an ordering relative to INGRESS, # as if it were added with no below or atop if (not v in orders) and (v not in (INGRESS, MAIN)): self.implicit_order.append((v, INGRESS)) - self.implicit_ingress_names.append(v) + self.implicit_ingress_alias_names.append(v) add_node(graph, v) for a, b in self.implicit_order: @@ -125,7 +129,7 @@ class Tweens(object): def sortroots(name): # sort roots so that roots (and their children) that depend only on # the ingress sort nearer the end (nearer the ingress) - if name in self.implicit_ingress_names: + if name in self.implicit_ingress_alias_names: return 1 children = graph[name][1:] for child in children: @@ -135,11 +139,11 @@ class Tweens(object): roots.sort(key=sortroots) - sorted_tweens = [] + sorted_aliases = [] while roots: root = roots.pop(0) - sorted_tweens.append(root) + sorted_aliases.append(root) children = graph[root][1:] for child in children: arcs = graph[child][0] @@ -156,8 +160,14 @@ class Tweens(object): cycledeps[k] = v[1:] raise CyclicDependencyError(cycledeps) - return [ (name, self.implicit_factories[name]) for name in - sorted_tweens if name not in (MAIN, INGRESS) ] + result = [] + + for alias in sorted_aliases: + if alias not in (MAIN, INGRESS): + name = self.implicit_aliases[alias] + result.append((name, self.implicit_factories[name])) + + return result def __call__(self, handler, registry): if self.explicit: @@ -185,7 +195,7 @@ def tween_factory_name(factory): '%s is not a suitable tween factory' % factory) return name -MAIN = 'main' -INGRESS = 'ingress' +MAIN = 'main-->' +INGRESS = '<--ingress' EXCVIEW = tween_factory_name(excview_tween_factory) -- cgit v1.2.3 From fd78f1b38496e2eda00e6efa08a52fdb7d922b99 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 02:39:40 -0400 Subject: add tween aliases --- pyramid/config.py | 36 +++++++-------- pyramid/paster.py | 52 ++++++++++++++-------- pyramid/tests/test_paster.py | 34 +++------------ pyramid/tests/test_tweens.py | 101 +++++++++++++++++++++++++++++++++++-------- pyramid/tweens.py | 89 +++++++++++++++++++++----------------- 5 files changed, 191 insertions(+), 121 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 6ae9a70ce..d947376c8 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -905,7 +905,7 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_tween(self, tween_factory, below=None, atop=None): + def add_tween(self, tween_factory, alias=None, below=None, atop=None): """ Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit of code that sits between the Pyramid router's main request handling @@ -922,25 +922,27 @@ class Configurator(object): .. note:: This feature is new as of Pyramid 1.1.1. """ - return self._add_tween(tween_factory, below=below, atop=atop, - explicit=False) + return self._add_tween(tween_factory, alias=alias, below=below, + atop=atop, explicit=False) - def _add_tween(self, tween_factory, below=None, atop=None, explicit=False): + def _add_tween(self, tween_factory, alias=None, below=None, atop=None, + explicit=False): tween_factory = self.maybe_dotted(tween_factory) name = tween_factory_name(tween_factory) - def register(): - registry = self.registry - tweens = registry.queryUtility(ITweens) - if tweens is None: - tweens = Tweens() - registry.registerUtility(tweens, ITweens) - tweens.add_implicit(tween_factory_name(excview_tween_factory), - excview_tween_factory, below=MAIN) - if explicit: - tweens.add_explicit(name, tween_factory) - else: - tweens.add_implicit(name, tween_factory, below=below, atop=atop) - self.action(('tween', name, explicit), register) + registry = self.registry + tweens = registry.queryUtility(ITweens) + if tweens is None: + tweens = Tweens() + registry.registerUtility(tweens, ITweens) + tweens.add_implicit(tween_factory_name(excview_tween_factory), + excview_tween_factory, alias='excview', + below=MAIN) + if explicit: + tweens.add_explicit(name, tween_factory) + else: + tweens.add_implicit(name, tween_factory, alias=alias, + below=below, atop=atop) + self.action(('tween', name, explicit)) @action_method def add_request_handler(self, factory, name): # pragma: no cover diff --git a/pyramid/paster.py b/pyramid/paster.py index e7ec8fb93..46d45f801 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -14,6 +14,9 @@ from pyramid.interfaces import ITweens from pyramid.scripting import prepare from pyramid.util import DottedNameResolver +from pyramid.tweens import MAIN +from pyramid.tweens import INGRESS + from pyramid.scaffolds import PyramidTemplate # bw compat zope.deprecation.deprecated( 'PyramidTemplate', ('pyramid.paster.PyramidTemplate was moved to ' @@ -567,6 +570,28 @@ class PTweensCommand(PCommand): def out(self, msg): # pragma: no cover print msg + + def show_implicit(self, tweens): + implicit = tweens.implicit() + fmt = '%-10s %-50s %-15s' + self.out(fmt % ('Position', 'Name', 'Alias')) + self.out(fmt % ( + '-'*len('Position'), '-'*len('Name'), '-'*len('Alias'))) + self.out(fmt % ('-', '-', MAIN)) + for pos, (name, _) in enumerate(implicit): + alias = tweens.name_to_alias.get(name, None) + self.out(fmt % (pos, name, alias)) + self.out(fmt % ('-', '-', INGRESS)) + + def show_explicit(self, tweens): + explicit = tweens.explicit + fmt = '%-10s %-65s' + self.out(fmt % ('Position', 'Name')) + self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) + self.out(fmt % ('-', MAIN)) + for pos, (name, _) in enumerate(explicit): + self.out(fmt % (pos, name)) + self.out(fmt % ('-', INGRESS)) def command(self): config_uri = self.args[0] @@ -574,31 +599,22 @@ class PTweensCommand(PCommand): registry = env['registry'] tweens = self._get_tweens(registry) if tweens is not None: - implicit = tweens.implicit() explicit = tweens.explicit - ordering = [] if explicit: self.out('"pyramid.tweens" config value set ' '(explicitly ordered tweens used)') self.out('') - ordering.append((explicit, - 'Explicit Tween Chain (used)')) - ordering.append((implicit, - 'Implicit Tween Chain (not used)')) + self.out('Explicit Tween Chain (used)') + self.out('') + self.show_explicit(tweens) + self.out('') + self.out('Implicit Tween Chain (not used)') + self.out('') + self.show_implicit(tweens) else: self.out('"pyramid.tweens" config value NOT set ' '(implicitly ordered tweens used)') self.out('') - ordering.append((implicit, '')) - for L, title in ordering: - if title: - self.out(title) - self.out('') - fmt = '%-10s %-30s' - self.out(fmt % ('Position', 'Name')) - self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) - self.out(fmt % ('(implied)', 'main')) - for pos, (name, item) in enumerate(L): - self.out(fmt % (pos, name)) - self.out(fmt % ('(implied)', 'ingress')) + self.out('Implicit Tween Chain') self.out('') + self.show_implicit(tweens) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index be7aa386e..36c3a51be 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -853,15 +853,9 @@ class TestPTweensCommand(unittest.TestCase): result = command.command() self.assertEqual(result, None) self.assertEqual( - L, - ['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)', - '', - 'Position Name ', - '-------- ---- ', - '(implied) main ', - '0 name ', - '(implied) ingress ', - '']) + L[0], + '"pyramid.tweens" config value NOT set (implicitly ordered tweens ' + 'used)') def test_command_implicit_and_explicit_tweens(self): command = self._makeOne() @@ -872,25 +866,8 @@ class TestPTweensCommand(unittest.TestCase): result = command.command() self.assertEqual(result, None) self.assertEqual( - L, - ['"pyramid.tweens" config value set (explicitly ordered tweens used)', - '', - 'Explicit Tween Chain (used)', - '', - 'Position Name ', - '-------- ---- ', - '(implied) main ', - '0 name2 ', - '(implied) ingress ', - '', - 'Implicit Tween Chain (not used)', - '', - 'Position Name ', - '-------- ---- ', - '(implied) main ', - '0 name ', - '(implied) ingress ', - '']) + L[0], + '"pyramid.tweens" config value set (explicitly ordered tweens used)') def test__get_tweens(self): command = self._makeOne() @@ -901,6 +878,7 @@ class DummyTweens(object): def __init__(self, implicit, explicit): self._implicit = implicit self.explicit = explicit + self.name_to_alias = {} def implicit(self): return self._implicit diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index c8c2e591c..127256c05 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -13,32 +13,75 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.explicit, [('name', 'factory'), ('name2', 'factory2')]) - def test_add_implicit(self): + def test_add_implicit_noaliases(self): from pyramid.tweens import INGRESS + from pyramid.tweens import MAIN + D = {MAIN:MAIN, INGRESS:INGRESS} tweens = self._makeOne() tweens.add_implicit('name', 'factory') - self.assertEqual(tweens.implicit_alias_names, ['name']) - self.assertEqual(tweens.implicit_factories, + self.assertEqual(tweens.names, ['name']) + self.assertEqual(tweens.factories, {'name':'factory'}) - self.assertEqual(tweens.implicit_ingress_alias_names, ['name']) - self.assertEqual(tweens.implicit_order, [('name', INGRESS)]) + self.assertEqual(tweens.alias_to_name, D) + self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.order, [('name', INGRESS)]) + self.assertEqual(tweens.ingress_alias_names, ['name']) tweens.add_implicit('name2', 'factory2') - self.assertEqual(tweens.implicit_alias_names, ['name', 'name2']) - self.assertEqual(tweens.implicit_factories, + self.assertEqual(tweens.names, ['name', 'name2']) + self.assertEqual(tweens.factories, {'name':'factory', 'name2':'factory2'}) - self.assertEqual(tweens.implicit_ingress_alias_names, ['name', 'name2']) - self.assertEqual(tweens.implicit_order, + self.assertEqual(tweens.alias_to_name, D) + self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.order, [('name', INGRESS), ('name2', INGRESS)]) + self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) tweens.add_implicit('name3', 'factory3', below='name2') - self.assertEqual(tweens.implicit_alias_names, + self.assertEqual(tweens.names, ['name', 'name2', 'name3']) - self.assertEqual(tweens.implicit_factories, + self.assertEqual(tweens.factories, {'name':'factory', 'name2':'factory2', 'name3':'factory3'}) - self.assertEqual(tweens.implicit_ingress_alias_names, ['name', 'name2']) - self.assertEqual(tweens.implicit_order, + self.assertEqual(tweens.alias_to_name, D) + self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.order, [('name', INGRESS), ('name2', INGRESS), ('name2', 'name3')]) + self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) + + def test_add_implicit_withaliases(self): + from pyramid.tweens import INGRESS + from pyramid.tweens import MAIN + D = {MAIN:MAIN, INGRESS:INGRESS} + tweens = self._makeOne() + tweens.add_implicit('name1', 'factory', alias='n1') + self.assertEqual(tweens.names, ['name1']) + self.assertEqual(tweens.factories, + {'name1':'factory'}) + self.assertEqual(tweens.alias_to_name['n1'], 'name1') + self.assertEqual(tweens.name_to_alias['name1'], 'n1') + self.assertEqual(tweens.order, [('n1', INGRESS)]) + self.assertEqual(tweens.ingress_alias_names, ['n1']) + tweens.add_implicit('name2', 'factory2', alias='n2') + self.assertEqual(tweens.names, ['name1', 'name2']) + self.assertEqual(tweens.factories, + {'name1':'factory', 'name2':'factory2'}) + self.assertEqual(tweens.alias_to_name['n2'], 'name2') + self.assertEqual(tweens.name_to_alias['name2'], 'n2') + self.assertEqual(tweens.order, + [('n1', INGRESS), ('n2', INGRESS)]) + self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2']) + tweens.add_implicit('name3', 'factory3', alias='n3', below='name2') + self.assertEqual(tweens.names, + ['name1', 'name2', 'name3']) + self.assertEqual(tweens.factories, + {'name1':'factory', 'name2':'factory2', + 'name3':'factory3'}) + self.assertEqual(tweens.alias_to_name['n3'], 'name3') + self.assertEqual(tweens.name_to_alias['name3'], 'n3') + self.assertEqual(tweens.order, + [('n1', INGRESS), ('n2', INGRESS), + ('name2', 'n3')]) + self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2']) def test___call___explicit(self): tweens = self._makeOne() @@ -55,9 +98,10 @@ class TestTweens(unittest.TestCase): return handler def factory2(handler, registry): return '123' - tweens.implicit_alias_names = ['name', 'name2'] - tweens.implicit_aliases = {'name':'name', 'name2':'name2'} - tweens.implicit_factories = {'name':factory1, 'name2':factory2} + tweens.names = ['name', 'name2'] + tweens.alias_to_name = {'name':'name', 'name2':'name2'} + tweens.name_to_alias = {'name':'name', 'name2':'name2'} + tweens.factories = {'name':factory1, 'name2':factory2} self.assertEqual(tweens(None, None), '123') def test___call___implicit_with_aliasnames_different_than_names(self): @@ -66,9 +110,10 @@ class TestTweens(unittest.TestCase): return handler def factory2(handler, registry): return '123' - tweens.implicit_alias_names = ['foo1', 'foo2'] - tweens.implicit_aliases = {'foo1':'name', 'foo2':'name2'} - tweens.implicit_factories = {'name':factory1, 'name2':factory2} + tweens.names = ['foo1', 'foo2'] + tweens.alias_to_name = {'foo1':'name', 'foo2':'name2'} + tweens.name_to_alias = {'name':'foo1', 'name2':'foo2'} + tweens.factories = {'name':factory1, 'name2':factory2} self.assertEqual(tweens(None, None), '123') def test_implicit_ordering_1(self): @@ -122,6 +167,24 @@ class TestTweens(unittest.TestCase): ('browserid', 'browserid_factory'), ('dbt', 'dbt_factory')]) + def test_implicit_ordering_withaliases(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', alias='e', below=MAIN) + add('auth', 'auth_factory', atop='b') + add('retry', 'retry_factory', below='t', atop='exceptionview') + add('browserid', 'browserid_factory', alias='b') + add('txnmgr', 'txnmgr_factory', alias='t', atop='exceptionview') + add('dbt', 'dbt_factory') + self.assertEqual(tweens.implicit(), + [('txnmgr', 'txnmgr_factory'), + ('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('auth', 'auth_factory'), + ('browserid', 'browserid_factory'), + ('dbt', 'dbt_factory')]) + def test_implicit_ordering_missing_partial(self): from pyramid.tweens import MAIN tweens = self._makeOne() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index ee46ec94a..c6e386bfe 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -61,34 +61,48 @@ class Tweens(object): implements(ITweens) def __init__(self): self.explicit = [] - self.implicit_alias_names = [] - self.implicit_factories = {} - self.implicit_order = [] - self.implicit_ingress_alias_names = [] - self.implicit_aliases = {} + self.names = [] + self.factories = {} + self.order = [] + self.ingress_alias_names = [] + self.alias_to_name = {INGRESS:INGRESS, MAIN:MAIN} + self.name_to_alias = {INGRESS:INGRESS, MAIN:MAIN} def add_explicit(self, name, factory): self.explicit.append((name, factory)) def add_implicit(self, name, factory, alias=None, below=None, atop=None): - if alias is None: + if alias is not None: + self.alias_to_name[alias] = name + self.name_to_alias[name] = alias + else: alias = name - self.implicit_aliases[alias] = name - self.implicit_alias_names.append(alias) - self.implicit_factories[name] = factory + self.names.append(name) + self.factories[name] = factory if below is None and atop is None: atop = INGRESS - self.implicit_ingress_alias_names.append(alias) + self.ingress_alias_names.append(alias) if below is not None: - order = (below, alias) - self.implicit_order.append(order) + self.order.append((below, alias)) if atop is not None: - order = (alias, atop) - self.implicit_order.append(order) + self.order.append((alias, atop)) def implicit(self): + order = [] roots = [] graph = {} + has_order = {} + aliases = [MAIN, INGRESS] + ingress_alias_names = self.ingress_alias_names[:] + + for name in self.names: + aliases.append(self.name_to_alias.get(name, name)) + + for a, b in self.order: + # try to convert both a and b to an alias + a = self.name_to_alias.get(a, a) + b = self.name_to_alias.get(b, b) + order.append((a, b)) def add_node(graph, node): if not graph.has_key(node): @@ -101,37 +115,34 @@ class Tweens(object): if tonode in roots: roots.remove(tonode) - aliases = [MAIN, INGRESS] + self.implicit_alias_names - - orders = {} - - for pos, (first, second) in enumerate(self.implicit_order): + # remove ordering information that mentions unknown names/aliases + for pos, (first, second) in enumerate(order): has_first = first in aliases has_second = second in aliases if (not has_first) or (not has_second): - self.implicit_order[pos] = None, None # FFF + order[pos] = None, None else: - orders[first] = orders[second] = True + has_order[first] = has_order[second] = True for v in aliases: # any alias that doesn't have an ordering after we detect all # nodes with orders should get an ordering relative to INGRESS, - # as if it were added with no below or atop - if (not v in orders) and (v not in (INGRESS, MAIN)): - self.implicit_order.append((v, INGRESS)) - self.implicit_ingress_alias_names.append(v) + # as if it were added with no below or atop in add_implicit + if (not v in has_order) and (v not in (INGRESS, MAIN)): + order.append((v, INGRESS)) + ingress_alias_names.append(v) add_node(graph, v) - for a, b in self.implicit_order: - if a is not None and b is not None: # see FFF above + for a, b in order: + if a is not None and b is not None: # deal with removed orders add_arc(graph, a, b) - def sortroots(name): - # sort roots so that roots (and their children) that depend only on - # the ingress sort nearer the end (nearer the ingress) - if name in self.implicit_ingress_alias_names: + def sortroots(alias): + # sort roots so that roots (and their children) that depend only + # on the ingress sort nearer the end (nearer the ingress) + if alias in ingress_alias_names: return 1 - children = graph[name][1:] + children = graph[alias][1:] for child in children: if sortroots(child) == 1: return 1 @@ -164,17 +175,17 @@ class Tweens(object): for alias in sorted_aliases: if alias not in (MAIN, INGRESS): - name = self.implicit_aliases[alias] - result.append((name, self.implicit_factories[name])) + name = self.alias_to_name.get(alias, alias) + result.append((name, self.factories[name])) return result def __call__(self, handler, registry): if self.explicit: - factories = self.explicit + use = self.explicit else: - factories = self.implicit() - for name, factory in factories: + use = self.implicit() + for name, factory in use: handler = factory(handler, registry) return handler @@ -195,7 +206,7 @@ def tween_factory_name(factory): '%s is not a suitable tween factory' % factory) return name -MAIN = 'main-->' -INGRESS = '<--ingress' +MAIN = 'MAIN' +INGRESS = 'INGRESS' EXCVIEW = tween_factory_name(excview_tween_factory) -- cgit v1.2.3 From b5416b743b2b4cd6770198323e8df117e2f47444 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 02:48:12 -0400 Subject: add a missing partial test when aliases are in use --- pyramid/tests/test_tweens.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index 127256c05..f5f74d8fd 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -226,6 +226,18 @@ class TestTweens(unittest.TestCase): ('exceptionview', 'excview_factory'), ('browserid', 'browserid_factory')]) + def test_implicit_ordering_missing_partial_with_aliases(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', alias='e', below=MAIN) + add('retry', 'retry_factory', below='txnmgr', atop='e') + add('browserid', 'browserid_factory') + self.assertEqual(tweens.implicit(), + [('retry', 'retry_factory'), + ('exceptionview', 'excview_factory'), + ('browserid', 'browserid_factory')]) + def test_implicit_ordering_conflict_direct(self): from pyramid.tweens import CyclicDependencyError tweens = self._makeOne() -- cgit v1.2.3 From 98dbaf4a9a369e28020c33f239135190dc44a086 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 02:48:44 -0400 Subject: remove dead code --- pyramid/tests/test_tweens.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index f5f74d8fd..ba04b4e53 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -50,8 +50,6 @@ class TestTweens(unittest.TestCase): def test_add_implicit_withaliases(self): from pyramid.tweens import INGRESS - from pyramid.tweens import MAIN - D = {MAIN:MAIN, INGRESS:INGRESS} tweens = self._makeOne() tweens.add_implicit('name1', 'factory', alias='n1') self.assertEqual(tweens.names, ['name1']) -- cgit v1.2.3 From 2ab791347cfd7e82179b6f55cd5913af7337f625 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 03:12:42 -0400 Subject: convert below/atop to under/over --- pyramid/config.py | 21 ++++++++++------- pyramid/tests/test_config.py | 26 ++++++++++++++++++++ pyramid/tests/test_tweens.py | 56 ++++++++++++++++++++++---------------------- pyramid/tweens.py | 20 ++++++++-------- 4 files changed, 77 insertions(+), 46 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index d947376c8..d6a07e43c 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -82,7 +82,7 @@ from pyramid.traversal import traversal_path from pyramid.tweens import excview_tween_factory from pyramid.tweens import Tweens from pyramid.tweens import tween_factory_name -from pyramid.tweens import MAIN +from pyramid.tweens import MAIN, INGRESS, EXCVIEW from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver from pyramid.util import WeakOrderedSet @@ -905,7 +905,7 @@ class Configurator(object): return self._derive_view(view, attr=attr, renderer=renderer) @action_method - def add_tween(self, tween_factory, alias=None, below=None, atop=None): + def add_tween(self, tween_factory, alias=None, under=None, over=None): """ Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit of code that sits between the Pyramid router's main request handling @@ -922,27 +922,32 @@ class Configurator(object): .. note:: This feature is new as of Pyramid 1.1.1. """ - return self._add_tween(tween_factory, alias=alias, below=below, - atop=atop, explicit=False) + return self._add_tween(tween_factory, alias=alias, under=under, + over=over, explicit=False) - def _add_tween(self, tween_factory, alias=None, below=None, atop=None, + def _add_tween(self, tween_factory, alias=None, under=None, over=None, explicit=False): tween_factory = self.maybe_dotted(tween_factory) name = tween_factory_name(tween_factory) + if alias in (MAIN, INGRESS): + raise ConfigurationError('%s is a reserved tween name' % alias) + registry = self.registry tweens = registry.queryUtility(ITweens) if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) tweens.add_implicit(tween_factory_name(excview_tween_factory), - excview_tween_factory, alias='excview', - below=MAIN) + excview_tween_factory, alias=EXCVIEW, + under=MAIN) if explicit: tweens.add_explicit(name, tween_factory) else: tweens.add_implicit(name, tween_factory, alias=alias, - below=below, atop=atop) + under=under, over=over) self.action(('tween', name, explicit)) + if not explicit and alias is not None: + self.action(('tween', alias, explicit)) @action_method def add_request_handler(self, factory, name): # pragma: no cover diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 598362ccb..8e9ce3c4b 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -685,6 +685,22 @@ pyramid.tests.test_config.dummy_include2""", config = self._makeOne() self.assertRaises(ConfigurationError, config.add_tween, pyramid.tests) + def test_add_tween_alias_ingress(self): + from pyramid.exceptions import ConfigurationError + from pyramid.tweens import INGRESS + config = self._makeOne() + self.assertRaises(ConfigurationError, + config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', + alias=INGRESS) + + def test_add_tween_alias_main(self): + from pyramid.exceptions import ConfigurationError + from pyramid.tweens import MAIN + config = self._makeOne() + self.assertRaises(ConfigurationError, + config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', + alias=MAIN) + def test_add_tweens_conflict(self): from zope.configuration.config import ConfigurationConflictError config = self._makeOne() @@ -692,6 +708,16 @@ pyramid.tests.test_config.dummy_include2""", config.add_tween('pyramid.tests.test_config.dummy_tween_factory') self.assertRaises(ConfigurationConflictError, config.commit) + def test_add_tweens_conflict_same_alias(self): + from zope.configuration.config import ConfigurationConflictError + class ATween(object): pass + atween1 = ATween() + atween2 = ATween() + config = self._makeOne() + config.add_tween(atween1, alias='a') + config.add_tween(atween2, alias='a') + self.assertRaises(ConfigurationConflictError, config.commit) + def test_add_subscriber_defaults(self): from zope.interface import implements from zope.interface import Interface diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index ba04b4e53..7fe800f00 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -35,7 +35,7 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.order, [('name', INGRESS), ('name2', INGRESS)]) self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) - tweens.add_implicit('name3', 'factory3', below='name2') + tweens.add_implicit('name3', 'factory3', under='name2') self.assertEqual(tweens.names, ['name', 'name2', 'name3']) self.assertEqual(tweens.factories, @@ -68,7 +68,7 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.order, [('n1', INGRESS), ('n2', INGRESS)]) self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2']) - tweens.add_implicit('name3', 'factory3', alias='n3', below='name2') + tweens.add_implicit('name3', 'factory3', alias='n3', under='name2') self.assertEqual(tweens.names, ['name1', 'name2', 'name3']) self.assertEqual(tweens.factories, @@ -125,7 +125,7 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() tweens.add_implicit('name1', 'factory1') - tweens.add_implicit('name2', 'factory2', below=MAIN) + tweens.add_implicit('name2', 'factory2', under=MAIN) self.assertEqual(tweens.implicit(), [('name2', 'factory2'), ('name1', 'factory1')]) @@ -133,12 +133,12 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('auth', 'auth_factory', atop='browserid') + add('auth', 'auth_factory', over='browserid') add('dbt', 'dbt_factory') - add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('retry', 'retry_factory', under='txnmgr', over='exceptionview') add('browserid', 'browserid_factory') - add('txnmgr', 'txnmgr_factory', atop='exceptionview') - add('exceptionview', 'excview_factory', below=MAIN) + add('txnmgr', 'txnmgr_factory', over='exceptionview') + add('exceptionview', 'excview_factory', under=MAIN) self.assertEqual(tweens.implicit(), [('txnmgr', 'txnmgr_factory'), ('retry', 'retry_factory'), @@ -151,11 +151,11 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', below=MAIN) - add('auth', 'auth_factory', atop='browserid') - add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('exceptionview', 'excview_factory', under=MAIN) + add('auth', 'auth_factory', over='browserid') + add('retry', 'retry_factory', under='txnmgr', over='exceptionview') add('browserid', 'browserid_factory') - add('txnmgr', 'txnmgr_factory', atop='exceptionview') + add('txnmgr', 'txnmgr_factory', over='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [('txnmgr', 'txnmgr_factory'), @@ -169,11 +169,11 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', alias='e', below=MAIN) - add('auth', 'auth_factory', atop='b') - add('retry', 'retry_factory', below='t', atop='exceptionview') + add('exceptionview', 'excview_factory', alias='e', under=MAIN) + add('auth', 'auth_factory', over='b') + add('retry', 'retry_factory', under='t', over='exceptionview') add('browserid', 'browserid_factory', alias='b') - add('txnmgr', 'txnmgr_factory', alias='t', atop='exceptionview') + add('txnmgr', 'txnmgr_factory', alias='t', over='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [('txnmgr', 'txnmgr_factory'), @@ -187,9 +187,9 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', below=MAIN) - add('auth', 'auth_factory', atop='browserid') - add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('exceptionview', 'excview_factory', under=MAIN) + add('auth', 'auth_factory', over='browserid') + add('retry', 'retry_factory', under='txnmgr', over='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), @@ -203,8 +203,8 @@ class TestTweens(unittest.TestCase): tweens = self._makeOne() add = tweens.add_implicit add('dbt', 'dbt_factory') - add('auth', 'auth_factory', atop='browserid') - add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('auth', 'auth_factory', over='browserid') + add('retry', 'retry_factory', under='txnmgr', over='exceptionview') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), [('dbt', 'dbt_factory'), @@ -216,8 +216,8 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', below=MAIN) - add('retry', 'retry_factory', below='txnmgr', atop='exceptionview') + add('exceptionview', 'excview_factory', under=MAIN) + add('retry', 'retry_factory', under='txnmgr', over='exceptionview') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), [('retry', 'retry_factory'), @@ -228,8 +228,8 @@ class TestTweens(unittest.TestCase): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', alias='e', below=MAIN) - add('retry', 'retry_factory', below='txnmgr', atop='e') + add('exceptionview', 'excview_factory', alias='e', under=MAIN) + add('retry', 'retry_factory', under='txnmgr', over='e') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), [('retry', 'retry_factory'), @@ -241,7 +241,7 @@ class TestTweens(unittest.TestCase): tweens = self._makeOne() add = tweens.add_implicit add('browserid', 'browserid_factory') - add('auth', 'auth_factory', atop='browserid', below='browserid') + add('auth', 'auth_factory', over='browserid', under='browserid') self.assertRaises(CyclicDependencyError, tweens.implicit) def test_implicit_ordering_conflict_indirect(self): @@ -249,8 +249,8 @@ class TestTweens(unittest.TestCase): tweens = self._makeOne() add = tweens.add_implicit add('browserid', 'browserid_factory') - add('auth', 'auth_factory', atop='browserid') - add('dbt', 'dbt_factory', below='browserid', atop='auth') + add('auth', 'auth_factory', over='browserid') + add('dbt', 'dbt_factory', under='browserid', over='auth') self.assertRaises(CyclicDependencyError, tweens.implicit) class TestCyclicDependencyError(unittest.TestCase): @@ -262,5 +262,5 @@ class TestCyclicDependencyError(unittest.TestCase): exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) result = str(exc) self.assertEqual(result, - "'a' sorts atop ['c', 'd']; 'c' sorts atop ['a']") + "'a' sorts over ['c', 'd']; 'c' sorts over ['a']") diff --git a/pyramid/tweens.py b/pyramid/tweens.py index c6e386bfe..1cb62999d 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -53,7 +53,7 @@ class CyclicDependencyError(Exception): for cycle in cycles: dependent = cycle dependees = cycles[cycle] - L.append('%r sorts atop %r' % (dependent, dependees)) + L.append('%r sorts over %r' % (dependent, dependees)) msg = '; '.join(L) return msg @@ -71,7 +71,7 @@ class Tweens(object): def add_explicit(self, name, factory): self.explicit.append((name, factory)) - def add_implicit(self, name, factory, alias=None, below=None, atop=None): + def add_implicit(self, name, factory, alias=None, under=None, over=None): if alias is not None: self.alias_to_name[alias] = name self.name_to_alias[name] = alias @@ -79,13 +79,13 @@ class Tweens(object): alias = name self.names.append(name) self.factories[name] = factory - if below is None and atop is None: - atop = INGRESS + if under is None and over is None: + over = INGRESS self.ingress_alias_names.append(alias) - if below is not None: - self.order.append((below, alias)) - if atop is not None: - self.order.append((alias, atop)) + if under is not None: + self.order.append((under, alias)) + if over is not None: + self.order.append((alias, over)) def implicit(self): order = [] @@ -127,7 +127,7 @@ class Tweens(object): for v in aliases: # any alias that doesn't have an ordering after we detect all # nodes with orders should get an ordering relative to INGRESS, - # as if it were added with no below or atop in add_implicit + # as if it were added with no under or over in add_implicit if (not v in has_order) and (v not in (INGRESS, MAIN)): order.append((v, INGRESS)) ingress_alias_names.append(v) @@ -208,5 +208,5 @@ def tween_factory_name(factory): MAIN = 'MAIN' INGRESS = 'INGRESS' -EXCVIEW = tween_factory_name(excview_tween_factory) +EXCVIEW = 'excview' -- cgit v1.2.3 From 4cce7990b99f52c85e07362042c5fdb16dafa825 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 05:16:30 -0400 Subject: reverse meaning of under/over --- pyramid/config.py | 6 +- pyramid/tests/test_config.py | 26 +++++--- pyramid/tests/test_tweens.py | 156 ++++++++++++++++++++++++------------------- pyramid/tweens.py | 18 ++--- 4 files changed, 115 insertions(+), 91 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index d6a07e43c..f8f65356e 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -939,12 +939,12 @@ class Configurator(object): registry.registerUtility(tweens, ITweens) tweens.add_implicit(tween_factory_name(excview_tween_factory), excview_tween_factory, alias=EXCVIEW, - under=MAIN) + over=MAIN) if explicit: tweens.add_explicit(name, tween_factory) else: - tweens.add_implicit(name, tween_factory, alias=alias, - under=under, over=over) + tweens.add_implicit(name, tween_factory, alias=alias, under=under, + over=over) self.action(('tween', name, explicit)) if not explicit and alias is not None: self.action(('tween', alias, explicit)) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 8e9ce3c4b..6db59a2aa 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -639,11 +639,15 @@ pyramid.tests.test_config.dummy_include2""", config.add_tween(factory2) config.commit() tweens = config.registry.queryUtility(ITweens) + implicit = tweens.implicit() self.assertEqual( - tweens.implicit(), - [('pyramid.tweens.excview_tween_factory', excview_tween_factory), - ('pyramid.tests.test_config.factory1', factory1), - ('pyramid.tests.test_config.factory2', factory2)]) + implicit, + [ + ('pyramid.tests.test_config.factory2', factory2), + ('pyramid.tests.test_config.factory1', factory1), + ('pyramid.tweens.excview_tween_factory', excview_tween_factory), + ] + ) def test_add_tween_dottedname(self): from pyramid.interfaces import ITweens @@ -655,9 +659,9 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual( tweens.implicit(), [ - ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.dummy_tween_factory', - dummy_tween_factory) + dummy_tween_factory), + ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ]) def test_add_tween_instance(self): @@ -671,13 +675,13 @@ pyramid.tests.test_config.dummy_include2""", tweens = config.registry.queryUtility(ITweens) implicit = tweens.implicit() self.assertEqual(len(implicit), 2) - self.assertEqual( - implicit[0], - ('pyramid.tweens.excview_tween_factory', excview_tween_factory)) self.assertTrue( - implicit[1][0].startswith( + implicit[0][0].startswith( 'pyramid.tests.test_config.ATween.')) - self.assertEqual(implicit[1][1], atween) + self.assertEqual(implicit[0][1], atween) + self.assertEqual( + implicit[1], + ('pyramid.tweens.excview_tween_factory', excview_tween_factory)) def test_add_tween_unsuitable(self): from pyramid.exceptions import ConfigurationError diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index 7fe800f00..a626afa11 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -24,7 +24,7 @@ class TestTweens(unittest.TestCase): {'name':'factory'}) self.assertEqual(tweens.alias_to_name, D) self.assertEqual(tweens.name_to_alias, D) - self.assertEqual(tweens.order, [('name', INGRESS)]) + self.assertEqual(tweens.order, [(INGRESS, 'name')]) self.assertEqual(tweens.ingress_alias_names, ['name']) tweens.add_implicit('name2', 'factory2') self.assertEqual(tweens.names, ['name', 'name2']) @@ -33,9 +33,9 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.alias_to_name, D) self.assertEqual(tweens.name_to_alias, D) self.assertEqual(tweens.order, - [('name', INGRESS), ('name2', INGRESS)]) + [(INGRESS, 'name'), (INGRESS, 'name2')]) self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) - tweens.add_implicit('name3', 'factory3', under='name2') + tweens.add_implicit('name3', 'factory3', over='name2') self.assertEqual(tweens.names, ['name', 'name2', 'name3']) self.assertEqual(tweens.factories, @@ -44,8 +44,8 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.alias_to_name, D) self.assertEqual(tweens.name_to_alias, D) self.assertEqual(tweens.order, - [('name', INGRESS), ('name2', INGRESS), - ('name2', 'name3')]) + [(INGRESS, 'name'), (INGRESS, 'name2'), + ('name3', 'name2')]) self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) def test_add_implicit_withaliases(self): @@ -57,7 +57,7 @@ class TestTweens(unittest.TestCase): {'name1':'factory'}) self.assertEqual(tweens.alias_to_name['n1'], 'name1') self.assertEqual(tweens.name_to_alias['name1'], 'n1') - self.assertEqual(tweens.order, [('n1', INGRESS)]) + self.assertEqual(tweens.order, [(INGRESS, 'n1')]) self.assertEqual(tweens.ingress_alias_names, ['n1']) tweens.add_implicit('name2', 'factory2', alias='n2') self.assertEqual(tweens.names, ['name1', 'name2']) @@ -66,9 +66,9 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.alias_to_name['n2'], 'name2') self.assertEqual(tweens.name_to_alias['name2'], 'n2') self.assertEqual(tweens.order, - [('n1', INGRESS), ('n2', INGRESS)]) + [(INGRESS, 'n1'), (INGRESS, 'n2')]) self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2']) - tweens.add_implicit('name3', 'factory3', alias='n3', under='name2') + tweens.add_implicit('name3', 'factory3', alias='n3', over='name2') self.assertEqual(tweens.names, ['name1', 'name2', 'name3']) self.assertEqual(tweens.factories, @@ -77,8 +77,8 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.alias_to_name['n3'], 'name3') self.assertEqual(tweens.name_to_alias['name3'], 'n3') self.assertEqual(tweens.order, - [('n1', INGRESS), ('n2', INGRESS), - ('name2', 'n3')]) + [(INGRESS, 'n1'), (INGRESS, 'n2'), + ('n3', 'name2')]) self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2']) def test___call___explicit(self): @@ -118,123 +118,143 @@ class TestTweens(unittest.TestCase): tweens = self._makeOne() tweens.add_implicit('name1', 'factory1') tweens.add_implicit('name2', 'factory2') - self.assertEqual(tweens.implicit(), [('name1', 'factory1'), - ('name2', 'factory2')]) + self.assertEqual(tweens.implicit(), + [ + ('name2', 'factory2'), + ('name1', 'factory1'), + ]) def test_implicit_ordering_2(self): from pyramid.tweens import MAIN tweens = self._makeOne() tweens.add_implicit('name1', 'factory1') - tweens.add_implicit('name2', 'factory2', under=MAIN) + tweens.add_implicit('name2', 'factory2', over=MAIN) self.assertEqual(tweens.implicit(), - [('name2', 'factory2'), ('name1', 'factory1')]) + [ + ('name1', 'factory1'), + ('name2', 'factory2'), + ]) def test_implicit_ordering_3(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('auth', 'auth_factory', over='browserid') + add('auth', 'auth_factory', under='browserid') add('dbt', 'dbt_factory') - add('retry', 'retry_factory', under='txnmgr', over='exceptionview') + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') - add('txnmgr', 'txnmgr_factory', over='exceptionview') - add('exceptionview', 'excview_factory', under=MAIN) + add('txnmgr', 'txnmgr_factory', under='exceptionview') + add('exceptionview', 'excview_factory', over=MAIN) self.assertEqual(tweens.implicit(), - [('txnmgr', 'txnmgr_factory'), - ('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('auth', 'auth_factory'), - ('browserid', 'browserid_factory'), - ('dbt', 'dbt_factory')]) + [ + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('dbt', 'dbt_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ('txnmgr', 'txnmgr_factory'), + ]) def test_implicit_ordering_4(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', under=MAIN) - add('auth', 'auth_factory', over='browserid') - add('retry', 'retry_factory', under='txnmgr', over='exceptionview') + add('exceptionview', 'excview_factory', over=MAIN) + add('auth', 'auth_factory', under='browserid') + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') - add('txnmgr', 'txnmgr_factory', over='exceptionview') + add('txnmgr', 'txnmgr_factory', under='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), - [('txnmgr', 'txnmgr_factory'), - ('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('auth', 'auth_factory'), - ('browserid', 'browserid_factory'), - ('dbt', 'dbt_factory')]) + [ + ('dbt', 'dbt_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ('txnmgr', 'txnmgr_factory'), + ]) def test_implicit_ordering_withaliases(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', alias='e', under=MAIN) - add('auth', 'auth_factory', over='b') - add('retry', 'retry_factory', under='t', over='exceptionview') + add('exceptionview', 'excview_factory', alias='e', over=MAIN) + add('auth', 'auth_factory', under='b') + add('retry', 'retry_factory', over='t', under='exceptionview') add('browserid', 'browserid_factory', alias='b') - add('txnmgr', 'txnmgr_factory', alias='t', over='exceptionview') + add('txnmgr', 'txnmgr_factory', alias='t', under='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), - [('txnmgr', 'txnmgr_factory'), - ('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('auth', 'auth_factory'), - ('browserid', 'browserid_factory'), - ('dbt', 'dbt_factory')]) + [ + ('dbt', 'dbt_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ('txnmgr', 'txnmgr_factory'), + ]) def test_implicit_ordering_missing_partial(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', under=MAIN) - add('auth', 'auth_factory', over='browserid') - add('retry', 'retry_factory', under='txnmgr', over='exceptionview') + add('exceptionview', 'excview_factory', over=MAIN) + add('auth', 'auth_factory', under='browserid') + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), - [('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('auth', 'auth_factory'), - ('browserid', 'browserid_factory'), - ('dbt', 'dbt_factory')]) + [ + ('dbt', 'dbt_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ]) def test_implicit_ordering_missing_partial2(self): tweens = self._makeOne() add = tweens.add_implicit add('dbt', 'dbt_factory') - add('auth', 'auth_factory', over='browserid') - add('retry', 'retry_factory', under='txnmgr', over='exceptionview') + add('auth', 'auth_factory', under='browserid') + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), - [('dbt', 'dbt_factory'), - ('auth', 'auth_factory'), - ('browserid', 'browserid_factory'), - ('retry', 'retry_factory')]) + [ + ('retry', 'retry_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('dbt', 'dbt_factory'), + ]) def test_implicit_ordering_missing_partial3(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', under=MAIN) - add('retry', 'retry_factory', under='txnmgr', over='exceptionview') + add('exceptionview', 'excview_factory', over=MAIN) + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), - [('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('browserid', 'browserid_factory')]) + [ + ('browserid', 'browserid_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ]) def test_implicit_ordering_missing_partial_with_aliases(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit - add('exceptionview', 'excview_factory', alias='e', under=MAIN) - add('retry', 'retry_factory', under='txnmgr', over='e') + add('exceptionview', 'excview_factory', alias='e', over=MAIN) + add('retry', 'retry_factory', over='txnmgr', under='e') add('browserid', 'browserid_factory') self.assertEqual(tweens.implicit(), - [('retry', 'retry_factory'), - ('exceptionview', 'excview_factory'), - ('browserid', 'browserid_factory')]) + [ + ('browserid', 'browserid_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ]) def test_implicit_ordering_conflict_direct(self): from pyramid.tweens import CyclicDependencyError diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 1cb62999d..9a7d432dc 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -80,7 +80,7 @@ class Tweens(object): self.names.append(name) self.factories[name] = factory if under is None and over is None: - over = INGRESS + under = INGRESS self.ingress_alias_names.append(alias) if under is not None: self.order.append((under, alias)) @@ -92,7 +92,7 @@ class Tweens(object): roots = [] graph = {} has_order = {} - aliases = [MAIN, INGRESS] + aliases = [INGRESS, MAIN] ingress_alias_names = self.ingress_alias_names[:] for name in self.names: @@ -129,7 +129,7 @@ class Tweens(object): # nodes with orders should get an ordering relative to INGRESS, # as if it were added with no under or over in add_implicit if (not v in has_order) and (v not in (INGRESS, MAIN)): - order.append((v, INGRESS)) + order.append((INGRESS, v)) ingress_alias_names.append(v) add_node(graph, v) @@ -139,14 +139,14 @@ class Tweens(object): def sortroots(alias): # sort roots so that roots (and their children) that depend only - # on the ingress sort nearer the end (nearer the ingress) + # on the ingress sort nearer the (nearer the ingress) if alias in ingress_alias_names: - return 1 + return -1 children = graph[alias][1:] for child in children: - if sortroots(child) == 1: - return 1 - return -1 + if sortroots(child) == -1: + return -1 + return 1 roots.sort(key=sortroots) @@ -185,7 +185,7 @@ class Tweens(object): use = self.explicit else: use = self.implicit() - for name, factory in use: + for name, factory in use[::-1]: handler = factory(handler, registry) return handler -- cgit v1.2.3 From ff165d793fced779ae6f73fda347a60597177a69 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 05:20:42 -0400 Subject: reverse names in paster ptweens --- pyramid/paster.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 46d45f801..47f9ebc34 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -577,21 +577,21 @@ class PTweensCommand(PCommand): self.out(fmt % ('Position', 'Name', 'Alias')) self.out(fmt % ( '-'*len('Position'), '-'*len('Name'), '-'*len('Alias'))) - self.out(fmt % ('-', '-', MAIN)) + self.out(fmt % ('-', '-', INGRESS)) for pos, (name, _) in enumerate(implicit): alias = tweens.name_to_alias.get(name, None) self.out(fmt % (pos, name, alias)) - self.out(fmt % ('-', '-', INGRESS)) + self.out(fmt % ('-', '-', MAIN)) def show_explicit(self, tweens): explicit = tweens.explicit fmt = '%-10s %-65s' self.out(fmt % ('Position', 'Name')) self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) - self.out(fmt % ('-', MAIN)) + self.out(fmt % ('-', INGRESS)) for pos, (name, _) in enumerate(explicit): self.out(fmt % (pos, name)) - self.out(fmt % ('-', INGRESS)) + self.out(fmt % ('-', MAIN)) def command(self): config_uri = self.args[0] -- cgit v1.2.3 From a956e9dcc65ceda76b1c32e3343eaf3924352763 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Mon, 8 Aug 2011 15:55:24 -0500 Subject: missing comma --- pyramid/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config.py b/pyramid/config.py index a12df8ef7..3fac6847d 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -138,7 +138,7 @@ class Configurator(object): The Configurator accepts a number of arguments: ``registry``, ``package``, ``settings``, ``root_factory``, ``authentication_policy``, - ``authorization_policy``, ``renderers`` ``debug_logger``, + ``authorization_policy``, ``renderers``, ``debug_logger``, ``locale_negotiator``, ``request_factory``, ``renderer_globals_factory``, ``default_permission``, ``session_factory``, ``default_view_mapper``, ``autocommit``, and ``exceptionresponse_view``. -- cgit v1.2.3 From 05f610e6ed66f8d5aca9d77ae0748feb0c8f8479 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 23:26:59 -0400 Subject: document under and over params --- docs/api/tweens.rst | 17 ++++ docs/glossary.rst | 14 +-- docs/narr/commandline.rst | 74 ++++++++------- docs/narr/hooks.rst | 208 +++++++++++++++++++++++++++++++++++-------- pyramid/config.py | 75 ++++++++++++++-- pyramid/tests/test_config.py | 21 +++++ 6 files changed, 327 insertions(+), 82 deletions(-) diff --git a/docs/api/tweens.rst b/docs/api/tweens.rst index 5fc15cb00..ddacd2cde 100644 --- a/docs/api/tweens.rst +++ b/docs/api/tweens.rst @@ -6,3 +6,20 @@ .. automodule:: pyramid.tweens .. autofunction:: excview_tween_factory + + .. attribute:: MAIN + + Constant representing the main Pyramid handling function, for use in + ``under`` and ``over`` arguments to + :meth:`pyramid.config.Configurator.add_tween`. + + .. attribute:: INGRESS + + Constant representing the request ingress, for use in ``under`` and + ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`. + + .. attribute:: EXCVIEW + + Constant representing the exception view tween, for use in ``under`` + and ``over`` arguments to + :meth:`pyramid.config.Configurator.add_tween`. diff --git a/docs/glossary.rst b/docs/glossary.rst index ccb62bbc8..f0ad81ded 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -920,11 +920,13 @@ Glossary tween A bit of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses - :app:`Pyramid` as its 'app'. A tween may be used by Pyramid framework - extensions, to provide, for example, Pyramid-specific view timing - support bookkeeping code that examines exceptions before they are - returned to the upstream WSGI application. Tweens behave a bit like - :mod:`WSGI` 'middleware' but they have the benefit of running in a + :app:`Pyramid` as its 'app'. The word "tween" is a contraction of + "between". A tween may be used by Pyramid framework extensions, to + provide, for example, Pyramid-specific view timing support, bookkeeping + code that examines exceptions before they are returned to the upstream + WSGI application, or a variety of other features. Tweens behave a bit + like :mod:`WSGI` 'middleware' but they have the benefit of running in a context in which they have access to the Pyramid :term:`application - registry` as well as the Pyramid rendering machinery. + registry` as well as the Pyramid rendering machinery. See + :ref:`registering_tweens`. diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 6f969196f..0f23c153c 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -306,15 +306,16 @@ is executed. Displaying "Tweens" ------------------- -A user can get a representation of both the implicit :term:`tween` ordering -(the ordering specified by calls to -:meth:`pyramid.config.Configurator.add_tween`) and the explicit tween -ordering (specified by the ``pyramid.tweens`` configuration setting) -orderings using the ``paster ptweens`` command. Handler factories which are -functions or classes will show up as a standard Python dotted name in the -``paster ptweens`` output. Tween factories which are *instances* will show -their module and class name; the Python object id of the instance will be -appended. +A :term:`tween` is a bit of code that sits between the main Pyramid +application request handler and the WSGI application which calls it. A user +can get a representation of both the implicit tween ordering (the ordering +specified by calls to :meth:`pyramid.config.Configurator.add_tween`) and the +explicit tween ordering (specified by the ``pyramid.tweens`` configuration +setting) orderings using the ``paster ptweens`` command. Handler factories +which are functions or classes will show up as a standard Python dotted name +in the ``paster ptweens`` output. Tween factories which are *instances* will +show their module and class name; the Python object id of the instance will +be appended. For example, here's the ``paster pwteens`` command run against a system configured without any explicit tweens: @@ -322,12 +323,17 @@ configured without any explicit tweens: .. code-block:: text :linenos: - [chrism@thinko starter]$ ../bin/paster ptweens development.ini + [chrism@thinko pyramid]$ paster ptweens development.ini "pyramid.tweens" config value NOT set (implicitly ordered tweens used) - Position Name - -------- ---- - 0 pyramid.router.excview_tween_factory + Implicit Tween Chain + + Position Name Alias + -------- ---- ----- + - - INGRESS + 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt + 1 pyramid.tweens.excview_tween_factory excview + - - MAIN Here's the ``paster pwteens`` command run against a system configured *with* explicit tweens defined in its ``development.ini`` file: @@ -335,22 +341,27 @@ explicit tweens defined in its ``development.ini`` file: .. code-block:: text :linenos: - [chrism@thinko starter]$ ../bin/paster ptweens development.ini + [chrism@thinko pyramid]$ paster ptweens development.ini "pyramid.tweens" config value set (explicitly ordered tweens used) Explicit Tween Chain (used) - Position Name - -------- ---- - 0 pyramid.tweens.excview_tween_factory - 1 starter.tween_factory1 - 2 starter.tween_factory2 + Position Name + -------- ---- + - INGRESS + 0 starter.tween_factory2 + 1 starter.tween_factory1 + 2 pyramid.tweens.excview_tween_factory + - MAIN Implicit Tween Chain (not used) - Position Name - -------- ---- - 0 pyramid.tweens.excview_tween_factory + Position Name Alias + -------- ---- ----- + - - INGRESS + 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt + 1 pyramid.tweens.excview_tween_factory excview + - - MAIN Here's the application configuration section of the ``development.ini`` used by the above ``paster ptweens`` command which reprorts that the explicit @@ -361,15 +372,18 @@ tween chain is used: [app:starter] use = egg:starter - pyramid.reload_templates = true - pyramid.debug_authorization = false - pyramid.debug_notfound = false - pyramid.debug_routematch = false - pyramid.debug_templates = true - pyramid.default_locale_name = en - pyramid.tweens = pyramid.tweens.excview_tween_factory + reload_templates = true + debug_authorization = false + debug_notfound = false + debug_routematch = false + debug_templates = true + default_locale_name = en + pyramid.include = pyramid_debugtoolbar + pyramid.tweens = starter.tween_factory2 starter.tween_factory1 - starter.tween_factory2 + pyramid.tweens.excview_tween_factory + +See :ref:`registering_tweens` for more information about tweens. .. _writing_a_script: diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 889e8d6d8..f7ee82821 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -836,11 +836,11 @@ Registering "Tweens" .. note:: Tweens are a feature which were added in Pyramid 1.1.1. They are not available in any previous version. -A :term:`tween` (think: "between") is a bit of code that sits between the -Pyramid router's main request handling function and the upstream WSGI -component that uses :app:`Pyramid` as its "app". This is a feature that may -be used by Pyramid framework extensions, to provide, for example, -Pyramid-specific view timing support bookkeeping code that examines +A :term:`tween` (a contraction of the word "between") is a bit of code that +sits between the Pyramid router's main request handling function and the +upstream WSGI component that uses :app:`Pyramid` as its "app". This is a +feature that may be used by Pyramid framework extensions, to provide, for +example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to the upstream WSGI application. Tweens behave a bit like :term:`WSGI` middleware but they have the benefit of running in a context in which they have access to the Pyramid @@ -857,8 +857,8 @@ Configurator. A tween factory must return a tween when it is called. A tween is a callable which accepts a :term:`request` object and returns a two-tuple a :term:`response` object. -Once you've created a tween factory, you can register it using the -:meth:`pyramid.config.Configurator.add_tween` method. +Once you've created a tween factory, you can register it into the implicit +tween chain using the :meth:`pyramid.config.Configurator.add_tween` method. Here's an example creating a tween factory and registering it: @@ -897,30 +897,145 @@ The ``request`` argument to a tween will be the request created by Pyramid's router when it receives a WSGI request. If more than one call to :meth:`pyramid.config.Configurator.add_tween` is -made within a single application configuration, the added tweens will be -chained together. The first tween factory added will be called with the -default Pyramid request handler as its ``handler`` argument, the second tween -factory added will be called with the result of the first tween factory as -its ``handler`` argument, and so on, ad infinitum. The Pyramid router will -use the outermost tween produced by this chain (the tween generated by the -very last tween factory added) as its request handler function. - -Pyramid will prevent the same tween factory from being added to the implicit -tween chain more than once using configuration conflict detection. If you -wish to add the same tween factory more than once in a configuration, you -should either: a) use a tween factory that is an instance rather than a -function or class, b) use a function or class as a tween factory with the -same logic as the other tween factory it conflicts with but with a different -``__name__`` attribute or c) call :meth:`pyramid.config.Configurator.commit` -between calls to :meth:`pyramid.config.Configurator.add_tween`. - -By default, the ordering of the chain is controlled entirely by the relative -ordering of calls to :meth:`pyramid.config.Configurator.add_tween`. However, -the deploying user can override tween inclusion and ordering entirely in his -Pyramid configuration using the ``pyramid.tweens`` settings value. When -used, this settings value will be a list of Python dotted names which imply -the ordering (and inclusion) of tween factories in the tween chain. For -example: +made within a single application configuration, the tweens will be chained +together at application startup time. The *first* tween factory added via +``add_tween`` will be called with the Pyramid exception view tween factory as +its ``handler`` argument, then the tween factory added directly after that +one will be called with the result of the first tween factory as its +``handler`` argument, and so on, ad infinitum until all tween factories have +been called. The Pyramid router will use the outermost tween produced by this +chain (the tween generated by the very last tween factory added) as its +request handler function. For example: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + config = Configurator() + config.add_tween('myapp.tween_factory1') + config.add_tween('myapp.tween_factory2') + +The above example will generate an implicit tween chain that looks like +this:: + + INGRESS (implicit) + myapp.tween_factory2 + myapp.tween_factory1 + pyramid.tweens.excview_tween_factory (implicit) + MAIN (implicit) + +By default, as described above, the ordering of the chain is controlled +entirely by the relative ordering of calls to +:meth:`pyramid.config.Configurator.add_tween`. However, the caller of +add_tween can provide an optional hint that can influence the implicit tween +chain ordering by supplying ``under`` or ``over`` (or both) arguments to +:meth:`~pyramid.config.Configurator.add_tween`. These hints are only used +used when an explicit tween chain is not used (when the ``pyramid.tweens`` +configuration value is not set). + +Allowable values for ``under`` or ``over`` (or both) are: + +- ``None`` (the default). + +- A :term:`dotted Python name` to a tween factory: a string representing the + predicted dotted name of a tween factory added in a call to ``add_tween`` + in the same configuration session. + +- A "tween alias": a string representing the predicted value of ``alias`` in + a separate call to ``add_tween`` in the same configuration session + +- One of the constants :attr:`pyramid.tweens.MAIN`, + :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`. + +Effectively, ``under`` means "closer to the main Pyramid application than", +``over`` means "closer to the request ingress than". + +For example, the following call to +:meth:`~pyramid.config.Configurator.add_tween` will attempt to place the +tween factory represented by ``myapp.tween_factory`` directly 'above' (in +``paster ptweens`` order) the main Pyramid request handler. + +.. code-block:: python + :linenos: + + import pyramid.tweens + + config.add_tween('myapp.tween_factory', over=pyramid.tweens.MAIN) + +The above example will generate an implicit tween chain that looks like +this:: + + INGRESS (implicit) + pyramid.tweens.excview_tween_factory (implicit) + myapp.tween_factory + MAIN (implicit) + +Likewise, calling the following call to +:meth:`~pyramid.config.Configurator.add_tween` will attempt to place this +tween factory 'above' the main handler but 'below' a separately added tween +factory: + +.. code-block:: python + :linenos: + + import pyramid.tweens + + config.add_tween('myapp.tween_factory1', + over=pyramid.tweens.MAIN) + config.add_tween('myapp.tween_factory2', + over=pyramid.tweens.MAIN, + under='myapp.tween_factory1') + +The above example will generate an implicit tween chain that looks like +this:: + + INGRESS (implicit) + pyramid.tweens.excview_tween_factory (implicit) + myapp.tween_factory1 + myapp.tween_factory2 + MAIN (implicit) + +Specifying neither ``over`` nor ``under`` is equivalent to specifying +``under=INGRESS``. + +If an ``under`` or ``over`` value is provided that does not match a tween +factory dotted name or alias in the current configuration, that value will be +ignored. It is not an error to provide an ``under`` or ``over`` value that +matches an unused tween factory. + +:meth:`~pyramid.config.Configurator.add_tween` also accepts an ``alias`` +argument. If ``alias`` is not ``None``, should be a string. The string will +represent a value that other callers of ``add_tween`` may pass as an +``under`` and ``over`` argument instead of a dotted name to a tween factory. +For example: + +.. code-block:: python + :linenos: + + import pyramid.tweens + + config.add_tween('myapp.tween_factory1', + alias='one' + over=pyramid.tweens.MAIN) + config.add_tween('myapp.tween_factory2', + alias='two' + over=pyramid.tweens.MAIN, + under='one') + +Alias names are only useful in relation to ``under`` and ``over`` values. +They cannot be used in explicit tween chain configuration, or anywhere else. + +Implicit tween ordering is obviously only best-effort. Pyramid will attempt +to provide an implicit order of tweens as best it can using hints provided by +calls to :meth:`~pyramid.config.Configurator.add_tween`, but because it's +only best-effort, if very precise tween ordering is required, the only +surefire way to get it is to use an explicit tween order. The deploying user +can override the implicit tween inclusion and ordering implied by calls to +:meth:`~pyramid.config.Configurator.add_tween` entirely by using the +``pyramid.tweens`` settings value. When used, this settings value must be a +list of Python dotted names which will override the ordering (and inclusion) +of tween factories in the implicit tween chain. For example: .. code-block:: ini :linenos: @@ -932,17 +1047,19 @@ example: pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true - pyramid.tweens = pyramid.tweens.excview_tween_factory - myapp.my_cool_tween_factory + pyramid.tweens = myapp.my_cool_tween_factory + pyramid.tweens.excview_tween_factory In the above configuration, calls made during configuration to :meth:`pyramid.config.Configurator.add_tween` are ignored, and the user is telling the system to use the tween factories he has listed in the -``pyramid.tweens`` configuration setting (each is a:term:`Python dotted name` -which points to a tween factory). The *last* tween factory in the -``pyramid.tweens`` list will be used as the producer of the effective +``pyramid.tweens`` configuration setting (each is a :term:`dotted Python +name` which points to a tween factory) instead of any tween factories added +via :meth:`pyramid.config.Configurator.add_tween`. The *first* tween factory +in the ``pyramid.tweens`` list will be used as the producer of the effective :app:`Pyramid` request handling function; it will wrap the tween factory -declared directly "above" it, ad infinitum. +declared directly "below" it, ad infinitum. The "main" Pyramid request +handler is implicit, and always "at the bottom". .. note:: Pyramid's own :term:`exception view` handling logic is implemented as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`. @@ -952,6 +1069,19 @@ declared directly "above" it, ad infinitum. ``pyramid.tweens`` configuration setting list explicitly. If it is not present, Pyramid will not perform exception view handling. -The ``paster ptweens`` command-line utility can be used to report the current -tween chain used by an application. See :ref:`displaying_tweens`. +Pyramid will prevent the same tween factory from being added to the tween +chain more than once using configuration conflict detection. If you wish to +add the same tween factory more than once in a configuration, you should +either: a) use a tween factory that is an instance rather than a function or +class, b) use a function or class as a tween factory with the same logic as +the other tween factory it conflicts with but with a different ``__name__`` +attribute or c) call :meth:`pyramid.config.Configurator.commit` between calls +to :meth:`pyramid.config.Configurator.add_tween`. +If a cycle is detected in implicit tween ordering when ``over`` and ``under`` +are used in any call to "add_tween", an exception will be raised at startup +time. + +The ``paster ptweens`` command-line utility can be used to report the current +implict and explicit tween chains used by an application. See +:ref:`displaying_tweens`. diff --git a/pyramid/config.py b/pyramid/config.py index f8f65356e..6034d6341 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -907,17 +907,78 @@ class Configurator(object): @action_method def add_tween(self, tween_factory, alias=None, under=None, over=None): """ - Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit - of code that sits between the Pyramid router's main request handling - function and the upstream WSGI component that uses :app:`Pyramid` as - its 'app'. This is a feature that may be used by Pyramid framework - extensions, to provide, for example, Pyramid-specific view timing - support bookkeeping code that examines exceptions before they are - returned to the upstream WSGI application. Tweens behave a bit like + Add a 'tween factory'. A :term:`tween` (a contraction of 'between') + is a bit of code that sits between the Pyramid router's main request + handling function and the upstream WSGI component that uses + :app:`Pyramid` as its 'app'. This is a feature that may be used by + Pyramid framework extensions, to provide, for example, + Pyramid-specific view timing support, bookkeeping code that examines + exceptions before they are returned to the upstream WSGI application, + or a variety of other features. Tweens behave a bit like :term:`WSGI` 'middleware' but they have the benefit of running in a context in which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. + .. note:: You can view the tween ordering configured into a given + Pyramid application by using the ``paster ptweens`` + command. See :ref:`displaying_tweens`. + + The ``alias`` argument, if it is not ``None``, should be a string. + The string will represent a value that other callers of ``add_tween`` + may pass as an ``under`` and ``over`` argument instead of a dotted + name to a tween factory. + + The ``under`` and ``over`` arguments allow the caller of + ``add_tween`` to provide a hint about where in the tween chain this + tween factory should be placed when an implicit tween chain is used. + These hints are only used used when an explicit tween chain is not + used (when the ``pyramid.tweens`` configuration value is not set). + Allowable values for ``under`` or ``over`` (or both) are: + + - ``None`` (the default). + + - A :term:`dotted Python name` to a tween factory: a string + representing the predicted dotted name of a tween factory added in + a call to ``add_tween`` in the same configuration session. + + - A tween alias: a string representing the predicted value of + ``alias`` in a separate call to ``add_tween`` in the same + configuration session + + - One of the constants :attr:`pyramid.tweens.MAIN`, + :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`. + + ``under`` means 'closer to the main Pyramid application than', + ``over`` means 'closer to the request ingress than'. + + For example, calling ``add_tween(factory, over=pyramid.tweens.MAIN)`` + will attempt to place the tween factory represented by ``factory`` + directly 'above' (in ``paster ptweens`` order) the main Pyramid + request handler. Likewise, calling ``add_tween(factory, + over=pyramid.tweens.MAIN, under='someothertween')`` will attempt to + place this tween factory 'above' the main handler but 'below' (a + fictional) 'someothertween' tween factory (which was presumably added + via ``add_tween(factory, alias='someothertween')``). + + If an ``under`` or ``over`` value is provided that does not match a + tween factory dotted name or alias in the current configuration, that + value will be ignored. It is not an error to provide an ``under`` or + ``over`` value that matches an unused tween factory. + + Specifying neither ``over`` nor ``under`` is equivalent to specifying + ``under=INGRESS``. + + Implicit tween ordering is obviously only best-effort. Pyramid will + attempt to present an implicit order of tweens as best it can, but + the only surefire way to get any particular ordering is to use an + explicit tween order. A user may always override the implicit tween + ordering by using an explicit ``pyramid.tweens`` configuration value + setting. + + ``alias``, ``under``, and ``over`` arguments are ignored when an + explicit tween chain is specified using the ``pyramid.tweens`` + configuration value. + For more information, see :ref:`registering_tweens`. .. note:: This feature is new as of Pyramid 1.1.1. diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 6db59a2aa..652fd94dd 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -649,6 +649,27 @@ pyramid.tests.test_config.dummy_include2""", ] ) + def test_add_tweens_names_with_underover(self): + from pyramid.interfaces import ITweens + from pyramid.tweens import excview_tween_factory + from pyramid.tweens import MAIN + def factory1(handler, registry): return handler + def factory2(handler, registry): return handler + config = self._makeOne() + config.add_tween(factory1, over=MAIN) + config.add_tween(factory2, over=MAIN, + under='pyramid.tests.test_config.factory1') + config.commit() + tweens = config.registry.queryUtility(ITweens) + implicit = tweens.implicit() + self.assertEqual( + implicit, + [ + ('pyramid.tweens.excview_tween_factory', excview_tween_factory), + ('pyramid.tests.test_config.factory1', factory1), + ('pyramid.tests.test_config.factory2', factory2), + ]) + def test_add_tween_dottedname(self): from pyramid.interfaces import ITweens from pyramid.tweens import excview_tween_factory -- cgit v1.2.3 From eecbc58df6150df5225612c24fc1cf5e2b74938f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 8 Aug 2011 23:50:13 -0400 Subject: fix test on jython --- pyramid/tests/test_tweens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index a626afa11..20a64908e 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -281,6 +281,6 @@ class TestCyclicDependencyError(unittest.TestCase): def test___str__(self): exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) result = str(exc) - self.assertEqual(result, - "'a' sorts over ['c', 'd']; 'c' sorts over ['a']") + self.assertTrue("'a' sorts over ['c', 'd']" in result) + self.assertTrue("'c' sorts over ['a']" in result) -- cgit v1.2.3 From 5a87a84b528221f156d84ac1ab71c618ccb010c8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 01:11:23 -0400 Subject: fix all scaffolds to use the debugtoolbar in development mode and pyramid_tm --- pyramid/scaffolds/alchemy/development.ini_tmpl | 8 ++------ pyramid/scaffolds/alchemy/production.ini_tmpl | 6 +----- pyramid/scaffolds/alchemy/setup.py_tmpl | 3 ++- pyramid/scaffolds/routesalchemy/development.ini_tmpl | 8 ++------ pyramid/scaffolds/routesalchemy/production.ini_tmpl | 6 +----- pyramid/scaffolds/routesalchemy/setup.py_tmpl | 3 ++- pyramid/scaffolds/starter/development.ini_tmpl | 2 +- pyramid/scaffolds/starter/setup.py_tmpl | 2 +- pyramid/scaffolds/zodb/development.ini_tmpl | 9 ++------- pyramid/scaffolds/zodb/production.ini_tmpl | 7 +------ pyramid/scaffolds/zodb/setup.py_tmpl | 4 ++-- 11 files changed, 17 insertions(+), 41 deletions(-) diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl index 06c29069d..4dc4c2461 100644 --- a/pyramid/scaffolds/alchemy/development.ini_tmpl +++ b/pyramid/scaffolds/alchemy/development.ini_tmpl @@ -7,19 +7,15 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] pipeline = - egg:WebError#evalerror - tm {{project}} -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl index 6025d9a14..dadf6c366 100644 --- a/pyramid/scaffolds/alchemy/production.ini_tmpl +++ b/pyramid/scaffolds/alchemy/production.ini_tmpl @@ -7,6 +7,7 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.db @@ -24,14 +25,9 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror - tm {{project}} [server:main] diff --git a/pyramid/scaffolds/alchemy/setup.py_tmpl b/pyramid/scaffolds/alchemy/setup.py_tmpl index 8e9da9755..183d8cc24 100644 --- a/pyramid/scaffolds/alchemy/setup.py_tmpl +++ b/pyramid/scaffolds/alchemy/setup.py_tmpl @@ -9,7 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', - 'repoze.tm2>=1.0b1', # default_commit_veto + 'pyramid_tm', + 'pyramid_debugtoolbar', 'sqlalchemy', 'zope.sqlalchemy', 'WebError', diff --git a/pyramid/scaffolds/routesalchemy/development.ini_tmpl b/pyramid/scaffolds/routesalchemy/development.ini_tmpl index 06c29069d..4dc4c2461 100644 --- a/pyramid/scaffolds/routesalchemy/development.ini_tmpl +++ b/pyramid/scaffolds/routesalchemy/development.ini_tmpl @@ -7,19 +7,15 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.db [pipeline:main] pipeline = - egg:WebError#evalerror - tm {{project}} -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/pyramid/scaffolds/routesalchemy/production.ini_tmpl b/pyramid/scaffolds/routesalchemy/production.ini_tmpl index 6025d9a14..9fd182ba3 100644 --- a/pyramid/scaffolds/routesalchemy/production.ini_tmpl +++ b/pyramid/scaffolds/routesalchemy/production.ini_tmpl @@ -7,6 +7,7 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.db @@ -24,14 +25,9 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror - tm {{project}} [server:main] diff --git a/pyramid/scaffolds/routesalchemy/setup.py_tmpl b/pyramid/scaffolds/routesalchemy/setup.py_tmpl index 936a5dde0..cd5eacfb7 100644 --- a/pyramid/scaffolds/routesalchemy/setup.py_tmpl +++ b/pyramid/scaffolds/routesalchemy/setup.py_tmpl @@ -11,7 +11,8 @@ requires = [ 'pyramid', 'SQLAlchemy', 'transaction', - 'repoze.tm2>=1.0b1', # default_commit_veto + 'pyramid_tm', + 'pyramid_debugtoolbar', 'zope.sqlalchemy', 'WebError', ] diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl index 1239dca3f..bbb448055 100644 --- a/pyramid/scaffolds/starter/development.ini_tmpl +++ b/pyramid/scaffolds/starter/development.ini_tmpl @@ -7,10 +7,10 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar [pipeline:main] pipeline = - egg:WebError#evalerror {{project}} [server:main] diff --git a/pyramid/scaffolds/starter/setup.py_tmpl b/pyramid/scaffolds/starter/setup.py_tmpl index bd2642627..84a20bd87 100644 --- a/pyramid/scaffolds/starter/setup.py_tmpl +++ b/pyramid/scaffolds/starter/setup.py_tmpl @@ -6,7 +6,7 @@ here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() -requires = ['pyramid', 'WebError'] +requires = ['pyramid', 'pyramid_debugtoolbar', 'WebError'] setup(name='{{project}}', version='0.0', diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl index 730300a82..d2d96bc05 100644 --- a/pyramid/scaffolds/zodb/development.ini_tmpl +++ b/pyramid/scaffolds/zodb/development.ini_tmpl @@ -7,21 +7,16 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm {{project}} -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl index 9fe8f4741..bb88c535d 100644 --- a/pyramid/scaffolds/zodb/production.ini_tmpl +++ b/pyramid/scaffolds/zodb/production.ini_tmpl @@ -7,6 +7,7 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 @@ -24,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm {{project}} [server:main] diff --git a/pyramid/scaffolds/zodb/setup.py_tmpl b/pyramid/scaffolds/zodb/setup.py_tmpl index 46759b514..87febd524 100644 --- a/pyramid/scaffolds/zodb/setup.py_tmpl +++ b/pyramid/scaffolds/zodb/setup.py_tmpl @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', # default_commit_veto - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', ] -- cgit v1.2.3 From 4dea53b033a2bb25857187bb9006bbd52df739bd Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 09:59:15 -0400 Subject: dont call hook_zca when we already are using the global reg --- docs/narr/zca.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/narr/zca.rst b/docs/narr/zca.rst index a99fd8b24..96aac6a80 100644 --- a/docs/narr/zca.rst +++ b/docs/narr/zca.rst @@ -245,7 +245,6 @@ registry at startup time instead of constructing a new one: globalreg = getGlobalSiteManager() config = Configurator(registry=globalreg) config.setup_registry(settings=settings) - config.hook_zca() config.include('some.other.application') return config.make_wsgi_app() -- cgit v1.2.3 From 0db92c11a3ae39cd399ef9da6a8d25d48f35881c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 12:53:33 -0400 Subject: set attempts to 3 explicitly in zodb apps --- pyramid/scaffolds/zodb/development.ini_tmpl | 1 + pyramid/scaffolds/zodb/production.ini_tmpl | 1 + 2 files changed, 2 insertions(+) diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl index d2d96bc05..0ff1a8fb8 100644 --- a/pyramid/scaffolds/zodb/development.ini_tmpl +++ b/pyramid/scaffolds/zodb/development.ini_tmpl @@ -9,6 +9,7 @@ pyramid.debug_templates = true pyramid.default_locale_name = en pyramid.include = pyramid_debugtoolbar pyramid_tm +pyramid_tm.attempts = 3 zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl index bb88c535d..76a406eb1 100644 --- a/pyramid/scaffolds/zodb/production.ini_tmpl +++ b/pyramid/scaffolds/zodb/production.ini_tmpl @@ -8,6 +8,7 @@ pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 -- cgit v1.2.3 From c6d9f191e920536fdb274d15ef956a5e8151bbc2 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 14:18:32 -0400 Subject: fix project.rst to deal with scaffold changes --- docs/narr/MyProject/development.ini | 3 ++- docs/narr/MyProject/production.ini | 1 + docs/narr/MyProject/setup.py | 2 +- docs/narr/project.png | Bin 84679 -> 128727 bytes docs/narr/project.rst | 51 +++++++++++++++++++++++++++--------- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index d0db3047c..1818e387b 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -1,15 +1,16 @@ [app:MyProject] use = egg:MyProject + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar [pipeline:main] pipeline = - egg:WebError#evalerror MyProject [server:main] diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini index d0ed9628c..7a5674d23 100644 --- a/docs/narr/MyProject/production.ini +++ b/docs/narr/MyProject/production.ini @@ -1,5 +1,6 @@ [app:MyProject] use = egg:MyProject + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py index a64d65ba6..a0b6fab9e 100644 --- a/docs/narr/MyProject/setup.py +++ b/docs/narr/MyProject/setup.py @@ -6,7 +6,7 @@ here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() -requires = ['pyramid', 'WebError'] +requires = ['pyramid', 'pyramid_debugtoolbar', 'WebError'] setup(name='MyProject', version='0.0', diff --git a/docs/narr/project.png b/docs/narr/project.png index da5bc870b..fc00ec086 100644 Binary files a/docs/narr/project.png and b/docs/narr/project.png differ diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 3b1b45eda..b4a0e1d45 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -99,18 +99,18 @@ We'll choose the ``pyramid_starter`` scaffold for this purpose. $ bin/paster create -t pyramid_starter -The above command uses the ``paster create`` command to create a project with the -``pyramid_starter`` scaffold. To use a different scaffold, such as +The above command uses the ``paster create`` command to create a project with +the ``pyramid_starter`` scaffold. To use a different scaffold, such as ``pyramid_routesalchemy``, you'd just change the last argument. For example: .. code-block:: text $ bin/paster create -t pyramid_routesalchemy -``paster create`` will ask you a single question: the *name* of the -project. You should use a string without spaces and with only letters -in it. Here's sample output from a run of ``paster create`` for a -project we name ``MyProject``: +``paster create`` will ask you a single question: the *name* of the project. +You should use a string without spaces and with only letters in it. Here's +sample output from a run of ``paster create`` for a project we name +``MyProject``: .. code-block:: text @@ -309,6 +309,23 @@ browser like what is displayed in the following image: This is the page shown by default when you visit an unmodified ``paster create`` -generated ``pyramid_starter`` application in a browser. +If you click on the image shown at the right hand top of the page ("^DT"), +you'll be presented with a debug toolbar that provides various niceties while +you're developing. This image will float above every HTML page served by +:app:`Pyramid` while you develop an application, and allows you show the +toolbar as necessary. Click on ``Hide`` to hide the toolbar and show the +image again. + +.. image:: project-debug.png + +For more information about what the debug toolbar allows you to do, see `the +documentation for pyramid_debugtoolbar +`_. + +The debug toolbar will not be shown (and all debugging will be turned off) +when you use the ``production.ini`` file instead of the ``development.ini`` +ini file to run the application. + .. sidebar:: Using an Alternate WSGI Server The code generated by a :app:`Pyramid` scaffold assumes that you @@ -419,8 +436,6 @@ serve``, as well as the deployment settings provided to that application. The generated ``development.ini`` file looks like so: -.. latexbroken? - .. literalinclude:: MyProject/development.ini :language: ini :linenos: @@ -489,6 +504,14 @@ information. options should be turned off for production applications, as template rendering is slowed when either is turned on. +The ``pyramid.include`` setting in the ``[app:MyProject]`` section tells +Pyramid to "include" configuration from another package. In this case, the +line ``pyramid.include = pyramid_debugtoolbar`` tells Pyramid to include +configuration from the ``pyramid_debugtoolbar`` package. This turns on a +debugging panel in development mode which will be shown on the right hand +side of the screen. Including the debug toolbar will also make it possible +to interactively debug exceptions when an error occurs. + Various other settings may exist in this section having to do with debugging or influencing runtime behavior of a :app:`Pyramid` application. See :ref:`environment_chapter` for more information about these settings. @@ -543,11 +566,13 @@ implementations. The ``production.ini`` file is a :term:`PasteDeploy` configuration file with a purpose much like that of ``development.ini``. However, it disables the -WebError interactive debugger, replacing it with a logger which outputs -exception messages to ``stderr`` by default. It also turns off template -development options such that templates are not automatically reloaded when -changed, and turns off all debugging options. You can use this file instead -of ``development.ini`` when you put your application into production. +debug toolbar, replacing it with a logger which outputs exception messages to +``stderr`` by default. It also turns off template development options such +that templates are not automatically reloaded when changed, and turns off all +debugging options. It allows you to configure a ``weberror#error_catcher`` +section that will cause exceptions to be sent to an email address when they +are uncaught. You can use this file instead of ``development.ini`` when you +put your application into production. .. index:: single: MANIFEST.in -- cgit v1.2.3 From 06247f77d3a7f6757c6082badb00154b6f626922 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 14:21:55 -0400 Subject: instructions about how to turn the toolbar off --- docs/narr/project.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index b4a0e1d45..baf4c86fa 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -326,6 +326,28 @@ The debug toolbar will not be shown (and all debugging will be turned off) when you use the ``production.ini`` file instead of the ``development.ini`` ini file to run the application. +You can also turn the debug toolbar off by editing ``development.ini`` and +commenting out the line ``pyramid.include = pyramid_debugtoolbar``. For +example, instead of: + +.. code-block:: ini + :linenos: + + [app:MyApp] + ... + pyramid.include = pyramid_debugtoolbar + +Put a hash mark in front of the ``pyramid.include`` line: + +.. code-block:: ini + :linenos: + + [app:MyApp] + ... + #pyramid.include = pyramid_debugtoolbar + +Then restart the application to see that the toolbar has been turned off. + .. sidebar:: Using an Alternate WSGI Server The code generated by a :app:`Pyramid` scaffold assumes that you -- cgit v1.2.3 From 7b771318141be700a5f4fd72dcafdd26d179211b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 14:22:09 -0400 Subject: add the debug image --- docs/narr/project-debug.png | Bin 0 -> 153807 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/narr/project-debug.png diff --git a/docs/narr/project-debug.png b/docs/narr/project-debug.png new file mode 100644 index 000000000..d13a91736 Binary files /dev/null and b/docs/narr/project-debug.png differ -- cgit v1.2.3 From ace57ba6a0443bbe4cb03fb6543243654c97eefa Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 21:09:40 -0400 Subject: convert tutorials to pyramid_tm+pyramid_debugtoolbar --- docs/tutorials/wiki/src/authorization/development.ini | 11 ++++------- docs/tutorials/wiki/src/authorization/production.ini | 9 +++------ docs/tutorials/wiki/src/authorization/setup.py | 4 ++-- docs/tutorials/wiki/src/basiclayout/development.ini | 11 ++++------- docs/tutorials/wiki/src/basiclayout/production.ini | 9 +++------ docs/tutorials/wiki/src/basiclayout/setup.py | 4 ++-- docs/tutorials/wiki/src/models/development.ini | 11 ++++------- docs/tutorials/wiki/src/models/production.ini | 9 +++------ docs/tutorials/wiki/src/models/setup.py | 4 ++-- docs/tutorials/wiki/src/tests/development.ini | 11 ++++------- docs/tutorials/wiki/src/tests/production.ini | 9 +++------ docs/tutorials/wiki/src/tests/setup.py | 4 ++-- docs/tutorials/wiki/src/views/development.ini | 11 ++++------- docs/tutorials/wiki/src/views/production.ini | 9 +++------ docs/tutorials/wiki/src/views/setup.py | 4 ++-- 15 files changed, 45 insertions(+), 75 deletions(-) diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 07800514e..8c60f4ea9 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -6,20 +6,17 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini index 217bdd260..2cfca5f80 100644 --- a/docs/tutorials/wiki/src/authorization/production.ini +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -6,6 +6,9 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] @@ -22,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial [server:main] diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index adfa70c9f..f3b404516 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', # default_commit_veto - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 04e341f2e..3913f45db 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -6,20 +6,17 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini index 217bdd260..2cfca5f80 100644 --- a/docs/tutorials/wiki/src/basiclayout/production.ini +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -6,6 +6,9 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] @@ -22,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial [server:main] diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index 2d540d65b..31833dc7f 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', # default_commit_veto - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', ] diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 07800514e..8c60f4ea9 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -6,20 +6,17 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini index 217bdd260..2cfca5f80 100644 --- a/docs/tutorials/wiki/src/models/production.ini +++ b/docs/tutorials/wiki/src/models/production.ini @@ -6,6 +6,9 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] @@ -22,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial [server:main] diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index 2d540d65b..31833dc7f 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', # default_commit_veto - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', ] diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 07800514e..8c60f4ea9 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -6,20 +6,17 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini index 217bdd260..2cfca5f80 100644 --- a/docs/tutorials/wiki/src/tests/production.ini +++ b/docs/tutorials/wiki/src/tests/production.ini @@ -6,6 +6,9 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] @@ -22,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial [server:main] diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index d2c8f5697..603bb6bac 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 04e341f2e..3913f45db 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -6,20 +6,17 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en +pyramid.include = pyramid_debugtoolbar + pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = - egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini index 217bdd260..2cfca5f80 100644 --- a/docs/tutorials/wiki/src/views/production.ini +++ b/docs/tutorials/wiki/src/views/production.ini @@ -6,6 +6,9 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en +pyramid.include = pyramid_tm +pyramid_tm.attempts = 3 + zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [filter:weberror] @@ -22,16 +25,10 @@ debug = false ;smtp_use_tls = ;error_message = -[filter:tm] -use = egg:repoze.tm2#tm -commit_veto = repoze.tm:default_commit_veto - [pipeline:main] pipeline = weberror egg:repoze.zodbconn#closer - egg:repoze.retry#retry - tm tutorial [server:main] diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index daa5e5eb1..6acc004c0 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -9,8 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm2>=1.0b1', # default_commit_veto - 'repoze.retry', + 'pyramid_tm', + 'pyramid_debugtoolbar', 'ZODB3', 'WebError', 'docutils', -- cgit v1.2.3 From c9c0ccc2179f63fa3c4030e64b6ccbf8c8996e08 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 22:23:37 -0400 Subject: add production.ini tests; assert that production.ini page output does not contain toolbar html --- pyramid/scaffolds/tests.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pyramid/scaffolds/tests.py b/pyramid/scaffolds/tests.py index e36943465..d5fcba67c 100644 --- a/pyramid/scaffolds/tests.py +++ b/pyramid/scaffolds/tests.py @@ -30,7 +30,6 @@ class TemplateTest(object): [os.path.join(self.directory, 'bin', 'python'), 'setup.py', 'develop']) os.chdir(self.directory) - subprocess.check_call(['bin/paster', 'create', '-t', tmpl_name, 'Dingle']) os.chdir('Dingle') @@ -38,23 +37,31 @@ class TemplateTest(object): subprocess.check_call([py, 'setup.py', 'install']) subprocess.check_call([py, 'setup.py', 'test']) paster = os.path.join(self.directory, 'bin', 'paster') - proc = subprocess.Popen([paster, 'serve', 'development.ini']) - try: - time.sleep(5) - proc.poll() - if proc.returncode is not None: - raise RuntimeError('didnt start') - conn = httplib.HTTPConnection('localhost:6543') - conn.request('GET', '/') - resp = conn.getresponse() - assert(resp.status == 200) - finally: - if hasattr(proc, 'terminate'): - # 2.6+ - proc.terminate() - else: - # 2.5 - os.kill(proc.pid, signal.SIGTERM) + for ininame, hastoolbar in (('development.ini', True), + ('production.ini', False)): + proc = subprocess.Popen([paster, 'serve', ininame]) + try: + time.sleep(5) + proc.poll() + if proc.returncode is not None: + raise RuntimeError('%s didnt start' % ininame) + conn = httplib.HTTPConnection('localhost:6543') + conn.request('GET', '/') + resp = conn.getresponse() + assert resp.status == 200, ininame + data = resp.read() + toolbarchunk = '
    Date: Tue, 9 Aug 2011 22:35:58 -0400 Subject: garden --- TODO.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/TODO.txt b/TODO.txt index 37c79035b..74e24bf2a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -7,9 +7,6 @@ Should-Have - Deprecate pyramid.security.view_execution_permitted (it only works for traversal). -- Merge https://github.com/Pylons/pyramid/pull/242 (IPython update; requires - test fixes and additional test coverage). - - Make "localizer" a property of request (instead of requiring "get_localizer(request)" @@ -17,6 +14,8 @@ Should-Have - Create a ``current_route_path`` function and make it a method of request. +- "static_path" API (omit host and port). + - Provide a way to set the authentication policy and the authorization policy during a config.include (they are related, so just exposing the currently underscored-private _set_auth* methods won't cut it). @@ -38,9 +37,6 @@ Should-Have ``paste.urlparser.StaticURLParser``, ``paste.auth.auth_tkt`` (cutnpaste or reimplement both). -- Use ``pyramid.include`` to add pyramid_debugtoolbar to all scaffolds and - remove weberror. - Nice-to-Have ------------ -- cgit v1.2.3 From 82bd0f9930bc19b9806acb7fd3c65c052eef3a28 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 9 Aug 2011 22:38:54 -0400 Subject: garden --- TODO.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TODO.txt b/TODO.txt index 74e24bf2a..ef36893df 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,11 @@ Pyramid TODOs Should-Have ----------- +- Replace weberror for email-out in all production.ini. + +- Come up with an analogue of repoze.zodbconn that doesn't require a closer + in the pipeline and use it in the ZODB scaffold and tutorial. + - Deprecate pyramid.security.view_execution_permitted (it only works for traversal). -- cgit v1.2.3 From 7d75b9711290da353077d87323ba0ccc1c1918ab Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 00:01:48 -0400 Subject: show members for event interfaces --- docs/api/interfaces.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 51a1963b5..9ab9eefc3 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -9,14 +9,19 @@ Event-Related Interfaces ++++++++++++++++++++++++ .. autointerface:: IApplicationCreated + :members: .. autointerface:: INewRequest + :members: .. autointerface:: IContextFound + :members: .. autointerface:: INewResponse + :members: .. autointerface:: IBeforeRender + :members: Other Interfaces ++++++++++++++++ -- cgit v1.2.3 From fecefff5f0c3a6aaafdd43d902aaed15edb8559e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 9 Aug 2011 23:26:12 -0500 Subject: Added the `pyramid.security.NO_PERMISSION_REQUIRED` constant. Removed the undocumented version from pyramid.interfaces. --- docs/api/config.rst | 2 +- docs/api/security.rst | 2 ++ docs/narr/security.rst | 9 +++++---- pyramid/config.py | 28 +++++++++++++++------------- pyramid/interfaces.py | 2 -- pyramid/security.py | 2 ++ pyramid/static.py | 3 ++- pyramid/tests/defpermbugapp/__init__.py | 3 ++- pyramid/tests/test_config.py | 3 ++- pyramid/tests/test_static.py | 4 ++-- 10 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 1a9bb6ba4..30c541905 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -44,7 +44,7 @@ .. automethod:: add_route - .. automethod:: add_static_view(name, path, cache_max_age=3600, permission='__no_permission_required__') + .. automethod:: add_static_view(name, path, cache_max_age=3600, permission=NO_PERMISSION_REQUIRED) .. automethod:: add_settings diff --git a/docs/api/security.rst b/docs/api/security.rst index de249355d..8cd9e5dae 100644 --- a/docs/api/security.rst +++ b/docs/api/security.rst @@ -57,6 +57,8 @@ Constants last ACE in an ACL in systems that use an "inheriting" security policy, representing the concept "don't inherit any other ACEs". +.. attribute:: NO_PERMISSION_REQUIRED + Return Values ------------- diff --git a/docs/narr/security.rst b/docs/narr/security.rst index ce304ed9f..a61578e21 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -200,9 +200,9 @@ When a default permission is registered: permission is ignored for that view registration, and the view-configuration-named permission is used. -- If a view configuration names an explicit permission as the string - ``__no_permission_required__``, the default permission is ignored, - and the view is registered *without* a permission (making it +- If a view configuration names the permission + :data:`pyramid.security.NO_PERMISSION_REQUIRED`, the default permission + is ignored, and the view is registered *without* a permission (making it available to all callers regardless of their credentials). .. warning:: @@ -210,7 +210,8 @@ When a default permission is registered: When you register a default permission, *all* views (even :term:`exception view` views) are protected by a permission. For all views which are truly meant to be anonymously accessible, you will need to associate the view's - configuration with the ``__no_permission_required__`` permission. + configuration with the :data:`pyramid.security.NO_PERMISSION_REQUIRED` + permission. .. index:: single: ACL diff --git a/pyramid/config.py b/pyramid/config.py index 45c5b743f..d3789bca4 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -71,6 +71,7 @@ from pyramid.renderers import RendererHelper from pyramid.request import route_request_iface from pyramid.asset import PackageOverrides from pyramid.asset import resolve_asset_spec +from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.settings import Settings from pyramid.static import StaticURLInfo from pyramid.threadlocal import get_current_registry @@ -1170,10 +1171,10 @@ class Configurator(object): ``default_permission`` argument, or if :meth:`pyramid.config.Configurator.set_default_permission` was used prior to this view registration. Pass the string - ``__no_permission_required__`` as the permission argument to - explicitly indicate that the view should always be - executable by entirely anonymous users, regardless of the - default permission, bypassing any :term:`authorization + :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the + permission argument to explicitly indicate that the view should + always be executable by entirely anonymous users, regardless of + the default permission, bypassing any :term:`authorization policy` that may be in effect. attr @@ -2400,10 +2401,11 @@ class Configurator(object): If a default permission is in effect, view configurations meant to create a truly anonymously accessible view (even :term:`exception view` views) *must* use the explicit permission string - ``__no_permission_required__`` as the permission. When this string - is used as the ``permission`` for a view configuration, the default - permission is ignored, and the view is registered, making it - available to all callers regardless of their credentials. + :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission. + When this string is used as the ``permission`` for a view + configuration, the default permission is ignored, and the view is + registered, making it available to all callers regardless of their + credentials. See also :ref:`setting_a_default_permission`. @@ -2530,10 +2532,10 @@ class Configurator(object): The ``permission`` keyword argument is used to specify the :term:`permission` required by a user to execute the static view. By - default, it is the string ``__no_permission_required__``. The - ``__no_permission_required__`` string is a special sentinel which - indicates that, even if a :term:`default permission` exists for the - current application, the static view should be renderered to + default, it is the string + :data:`pyramid.security.NO_PERMISSION_REQUIRED`, a special sentinel + which indicates that, even if a :term:`default permission` exists for + the current application, the static view should be renderered to completely anonymous users. This default value is permissive because, in most web apps, static assets seldom need protection from viewing. If ``permission`` is specified, the security checking will @@ -3132,7 +3134,7 @@ class ViewDeriver(object): @wraps_view def secured_view(self, view): permission = self.kw.get('permission') - if permission == '__no_permission_required__': + if permission == NO_PERMISSION_REQUIRED: # allow views registered within configurations that have a # default permission to explicitly override the default # permission, replacing it with no permission at all diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index d97632018..7666eb020 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -866,8 +866,6 @@ class ISession(Interface): def __contains__(key): """Return true if a key exists in the mapping.""" -NO_PERMISSION_REQUIRED = '__no_permission_required__' - class IRendererInfo(Interface): """ An object implementing this interface is passed to every :term:`renderer factory` constructor as its only argument (conventionally diff --git a/pyramid/security.py b/pyramid/security.py index 6cf63b0b3..a001f7073 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -24,6 +24,8 @@ class AllPermissionsList(object): ALL_PERMISSIONS = AllPermissionsList() DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS) +NO_PERMISSION_REQUIRED = '__no_permission_required__' + def has_permission(permission, context, request): """ Provided a permission (a string or unicode object), a context (a :term:`resource` instance) and a request object, return an diff --git a/pyramid/static.py b/pyramid/static.py index 9d8afc09b..b1fab066f 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -14,6 +14,7 @@ from pyramid.asset import resolve_asset_spec from pyramid.interfaces import IStaticURLInfo from pyramid.path import caller_package from pyramid.request import call_app_with_subpath_as_path_info +from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.url import route_url class PackageURLParser(StaticURLParser): @@ -149,7 +150,7 @@ class StaticURLInfo(object): if permission is None: permission = extra.pop('permission', None) if permission is None: - permission = '__no_permission_required__' + permission = NO_PERMISSION_REQUIRED context = extra.pop('view_context', None) if context is None: diff --git a/pyramid/tests/defpermbugapp/__init__.py b/pyramid/tests/defpermbugapp/__init__.py index 957bd08f6..49602749c 100644 --- a/pyramid/tests/defpermbugapp/__init__.py +++ b/pyramid/tests/defpermbugapp/__init__.py @@ -1,4 +1,5 @@ from webob import Response +from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.view import view_config @view_config(name='x') @@ -9,7 +10,7 @@ def x_view(request): # pragma: no cover def y_view(request): # pragma: no cover return Response('this is private too!') -@view_config(name='z', permission='__no_permission_required__') +@view_config(name='z', permission=NO_PERMISSION_REQUIRED) def z_view(request): return Response('this is public') diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 652fd94dd..3febe86bd 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -4166,13 +4166,14 @@ class TestViewDeriver(unittest.TestCase): self.assertEqual(permitted, False) def test_debug_auth_permission_authpol_overridden(self): + from pyramid.security import NO_PERMISSION_REQUIRED response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() self._registerSecurityPolicy(False) - deriver = self._makeOne(permission='__no_permission_required__') + deriver = self._makeOne(permission=NO_PERMISSION_REQUIRED) result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index a15459da2..d698ca4f2 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -410,6 +410,7 @@ class TestStaticURLInfo(unittest.TestCase): self.assertEqual(inst.registrations, expected) def test_add_viewname(self): + from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.static import static_view config = DummyConfig() inst = self._makeOne(config) @@ -417,8 +418,7 @@ class TestStaticURLInfo(unittest.TestCase): expected = [('view/', 'anotherpackage:path/', False)] self.assertEqual(inst.registrations, expected) self.assertEqual(config.route_args, ('view/', 'view/*subpath')) - self.assertEqual(config.view_kw['permission'], - '__no_permission_required__') + self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED) self.assertEqual(config.view_kw['view'].__class__, static_view) self.assertEqual(config.view_kw['view'].app.cache_max_age, 1) -- cgit v1.2.3 From 1e88dbb96c895f2978fb440221bec367d7ecdb0a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 00:34:13 -0400 Subject: add note about NO_PERMISSION_REQUIRED --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index b6f33715e..87b8ca53f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,12 @@ Next release Features -------- +- Added a ``pyramid.security.NO_PERMISSION_REQUIRED`` constant for use in + ``permission=`` statements to view configuration. This constant has a + value of the string ``__no_permission_required__``. This string value was + previously referred to in documentation; now the documentation uses the + constant. + - Added a decorator-based way to configure a response adapter: ``pyramid.response.response_adapter``. This decorator has the same use as ``pyramid.config.Configurator.add_response_adapter`` but it's declarative. -- cgit v1.2.3 From 30763035bb7dbcbf1a5adcf70ff915af8f950b85 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 01:21:40 -0400 Subject: simplify aliasing logic a bit (thanks raydeo) --- pyramid/tests/test_tweens.py | 14 +++++++------- pyramid/tweens.py | 9 ++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index 20a64908e..54297cb81 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -22,16 +22,16 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.names, ['name']) self.assertEqual(tweens.factories, {'name':'factory'}) - self.assertEqual(tweens.alias_to_name, D) - self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.alias_to_name['name'], 'name') + self.assertEqual(tweens.name_to_alias['name'], 'name') self.assertEqual(tweens.order, [(INGRESS, 'name')]) self.assertEqual(tweens.ingress_alias_names, ['name']) tweens.add_implicit('name2', 'factory2') self.assertEqual(tweens.names, ['name', 'name2']) self.assertEqual(tweens.factories, {'name':'factory', 'name2':'factory2'}) - self.assertEqual(tweens.alias_to_name, D) - self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.alias_to_name['name2'], 'name2') + self.assertEqual(tweens.name_to_alias['name2'], 'name2') self.assertEqual(tweens.order, [(INGRESS, 'name'), (INGRESS, 'name2')]) self.assertEqual(tweens.ingress_alias_names, ['name', 'name2']) @@ -41,8 +41,8 @@ class TestTweens(unittest.TestCase): self.assertEqual(tweens.factories, {'name':'factory', 'name2':'factory2', 'name3':'factory3'}) - self.assertEqual(tweens.alias_to_name, D) - self.assertEqual(tweens.name_to_alias, D) + self.assertEqual(tweens.alias_to_name['name3'], 'name3') + self.assertEqual(tweens.name_to_alias['name3'], 'name3') self.assertEqual(tweens.order, [(INGRESS, 'name'), (INGRESS, 'name2'), ('name3', 'name2')]) @@ -108,7 +108,7 @@ class TestTweens(unittest.TestCase): return handler def factory2(handler, registry): return '123' - tweens.names = ['foo1', 'foo2'] + tweens.names = ['name', 'name2'] tweens.alias_to_name = {'foo1':'name', 'foo2':'name2'} tweens.name_to_alias = {'name':'foo1', 'name2':'foo2'} tweens.factories = {'name':factory1, 'name2':factory2} diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 9a7d432dc..a9426c456 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -72,11 +72,10 @@ class Tweens(object): self.explicit.append((name, factory)) def add_implicit(self, name, factory, alias=None, under=None, over=None): - if alias is not None: - self.alias_to_name[alias] = name - self.name_to_alias[name] = alias - else: + if alias is None: alias = name + self.alias_to_name[alias] = name + self.name_to_alias[name] = alias self.names.append(name) self.factories[name] = factory if under is None and over is None: @@ -96,7 +95,7 @@ class Tweens(object): ingress_alias_names = self.ingress_alias_names[:] for name in self.names: - aliases.append(self.name_to_alias.get(name, name)) + aliases.append(self.name_to_alias[name]) for a, b in self.order: # try to convert both a and b to an alias -- cgit v1.2.3 From ddde5b6499796060831340f022da4d0975b83ccc Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 05:37:32 -0400 Subject: unused code --- pyramid/tests/test_tweens.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index 54297cb81..b18334f74 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -16,7 +16,6 @@ class TestTweens(unittest.TestCase): def test_add_implicit_noaliases(self): from pyramid.tweens import INGRESS from pyramid.tweens import MAIN - D = {MAIN:MAIN, INGRESS:INGRESS} tweens = self._makeOne() tweens.add_implicit('name', 'factory') self.assertEqual(tweens.names, ['name']) -- cgit v1.2.3 From 0818584847df63dbbe88ac5d1447ffbbb820eb08 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 08:38:16 -0400 Subject: garden --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index ef36893df..42de96a38 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,6 +4,9 @@ Pyramid TODOs Should-Have ----------- +- BeforeRender event subclasses dict but implements a bunch of shit. Its + repr is currently broken (it always shows empty). Decide what to do. + - Replace weberror for email-out in all production.ini. - Come up with an analogue of repoze.zodbconn that doesn't require a closer -- cgit v1.2.3 From 1939d00839e805f44680512b7952f4c1aff32a7c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 14:30:00 -0400 Subject: - Fixed an issue with the default renderer not working at certain times. See https://github.com/Pylons/pyramid/issues/249 Closes #249. --- CHANGES.txt | 6 ++++++ pyramid/renderers.py | 3 ++- pyramid/tests/test_renderers.py | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 87b8ca53f..208662150 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -75,6 +75,12 @@ Documentation - Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative chapter. +Bug Fixes +--------- + +- Fixed an issue with the default renderer not working at certain times. See + https://github.com/Pylons/pyramid/issues/249 + 1.1 (2011-07-22) ================ diff --git a/pyramid/renderers.py b/pyramid/renderers.py index a06067c97..dce6de140 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -354,7 +354,8 @@ class RendererHelper(object): if name and '.' in name: rtype = os.path.splitext(name)[1] else: - rtype = name + # important.. must be a string; cannot be None; see issue 249 + rtype = name or '' if registry is None: registry = get_current_registry() diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index cc36d69bf..b5c5d1242 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -486,6 +486,13 @@ class TestRendererHelper(unittest.TestCase): helper = self._makeOne(registry=Dummy) self.assertEqual(helper.settings, {}) + def test_settings_registry_name_is_None(self): + class Dummy(object): + settings = None + helper = self._makeOne(registry=Dummy) + self.assertEqual(helper.name, None) + self.assertEqual(helper.type, '') + def test_settings_registry_settings_is_not_None(self): class Dummy(object): settings = {'a':1} -- cgit v1.2.3 From bfbfd803d071529f07cbfb58216b8d32883ddb52 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 14:45:28 -0400 Subject: # we override the initializer to avoid calling get_current_registry # (it will return a reference to the global registry when this # thing is called at module scope; we don't want that). --- pyramid/renderers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index dce6de140..6f9b87698 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -478,6 +478,19 @@ class NullRendererHelper(RendererHelper): useful for testing purposes and special case view configuration registrations that want to use the view configuration machinery but do not want actual rendering to happen .""" + def __init__(self, name=None, package=None, registry=None): + # we override the initializer to avoid calling get_current_registry + # (it will return a reference to the global registry when this + # thing is called at module scope; we don't want that). + self.name = None + self.package = None + self.type = '' + self.registry = None + + @property + def settings(self): + return get_current_registry().settings or {} + def render_view(self, request, value, view, context): return value -- cgit v1.2.3 From 04ca37b938fc99c05b00e4cb817dc0fe1bebc21b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 18:21:47 -0400 Subject: remove bogus sortroots; add some more ordering tests; prevent people from attempting to add tweens over ingress or under main --- pyramid/config.py | 8 +++++++- pyramid/tests/test_config.py | 16 ++++++++++++++++ pyramid/tests/test_tweens.py | 41 ++++++++++++++++++++++++++++++++++++++++- pyramid/tweens.py | 31 +++++++++---------------------- 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index d3789bca4..917b689b1 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -993,7 +993,13 @@ class Configurator(object): name = tween_factory_name(tween_factory) if alias in (MAIN, INGRESS): raise ConfigurationError('%s is a reserved tween name' % alias) - + + if over is INGRESS: + raise ConfigurationError('%s cannot be over INGRESS') + + if under is MAIN: + raise ConfigurationError('%s cannot be under MAIN') + registry = self.registry tweens = registry.queryUtility(ITweens) if tweens is None: diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 3febe86bd..5d7caef03 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -743,6 +743,22 @@ pyramid.tests.test_config.dummy_include2""", config.add_tween(atween2, alias='a') self.assertRaises(ConfigurationConflictError, config.commit) + def test_add_tween_over_ingress(self): + from pyramid.exceptions import ConfigurationError + from pyramid.tweens import INGRESS + config = self._makeOne() + self.assertRaises(ConfigurationError, + config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', + over=INGRESS) + + def test_add_tween_under_main(self): + from pyramid.exceptions import ConfigurationError + from pyramid.tweens import MAIN + config = self._makeOne() + self.assertRaises(ConfigurationError, + config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', + under=MAIN) + def test_add_subscriber_defaults(self): from zope.interface import implements from zope.interface import Interface diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index b18334f74..5fa999e8a 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -15,7 +15,6 @@ class TestTweens(unittest.TestCase): def test_add_implicit_noaliases(self): from pyramid.tweens import INGRESS - from pyramid.tweens import MAIN tweens = self._makeOne() tweens.add_implicit('name', 'factory') self.assertEqual(tweens.names, ['name']) @@ -174,6 +173,26 @@ class TestTweens(unittest.TestCase): ('txnmgr', 'txnmgr_factory'), ]) + def test_implicit_ordering_5(self): + from pyramid.tweens import MAIN, INGRESS + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', over=MAIN) + add('auth', 'auth_factory', under=INGRESS) + add('retry', 'retry_factory', over='txnmgr', under='exceptionview') + add('browserid', 'browserid_factory', under=INGRESS) + add('txnmgr', 'txnmgr_factory', under='exceptionview', over=MAIN) + add('dbt', 'dbt_factory') + self.assertEqual(tweens.implicit(), + [ + ('dbt', 'dbt_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ('txnmgr', 'txnmgr_factory'), + ]) + def test_implicit_ordering_withaliases(self): from pyramid.tweens import MAIN tweens = self._makeOne() @@ -194,6 +213,26 @@ class TestTweens(unittest.TestCase): ('txnmgr', 'txnmgr_factory'), ]) + def test_implicit_ordering_withaliases2(self): + from pyramid.tweens import MAIN + tweens = self._makeOne() + add = tweens.add_implicit + add('exceptionview', 'excview_factory', alias='e', over=MAIN) + add('auth', 'auth_factory', alias='a', under='b') + add('retry', 'retry_factory', alias='r', over='t', under='e') + add('browserid', 'browserid_factory', alias='b') + add('txnmgr', 'txnmgr_factory', alias='t', under='e') + add('dbt', 'dbt_factory', alias='d') + self.assertEqual(tweens.implicit(), + [ + ('dbt', 'dbt_factory'), + ('browserid', 'browserid_factory'), + ('auth', 'auth_factory'), + ('exceptionview', 'excview_factory'), + ('retry', 'retry_factory'), + ('txnmgr', 'txnmgr_factory'), + ]) + def test_implicit_ordering_missing_partial(self): from pyramid.tweens import MAIN tweens = self._makeOne() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index a9426c456..90ff67267 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -87,7 +87,7 @@ class Tweens(object): self.order.append((alias, over)) def implicit(self): - order = [] + order = [(INGRESS, MAIN)] roots = [] graph = {} has_order = {} @@ -103,12 +103,12 @@ class Tweens(object): b = self.name_to_alias.get(b, b) order.append((a, b)) - def add_node(graph, node): + def add_node(node): if not graph.has_key(node): roots.append(node) graph[node] = [0] # 0 = number of arcs coming into this node - def add_arc(graph, fromnode, tonode): + def add_arc(fromnode, tonode): graph[fromnode].append(tonode) graph[tonode][0] += 1 if tonode in roots: @@ -123,31 +123,18 @@ class Tweens(object): else: has_order[first] = has_order[second] = True - for v in aliases: + for alias in aliases: # any alias that doesn't have an ordering after we detect all # nodes with orders should get an ordering relative to INGRESS, # as if it were added with no under or over in add_implicit - if (not v in has_order) and (v not in (INGRESS, MAIN)): - order.append((INGRESS, v)) - ingress_alias_names.append(v) - add_node(graph, v) + if (not alias in has_order) and (alias not in (INGRESS, MAIN)): + order.append((INGRESS, alias)) + ingress_alias_names.append(alias) + add_node(alias) for a, b in order: if a is not None and b is not None: # deal with removed orders - add_arc(graph, a, b) - - def sortroots(alias): - # sort roots so that roots (and their children) that depend only - # on the ingress sort nearer the (nearer the ingress) - if alias in ingress_alias_names: - return -1 - children = graph[alias][1:] - for child in children: - if sortroots(child) == -1: - return -1 - return 1 - - roots.sort(key=sortroots) + add_arc(a, b) sorted_aliases = [] -- cgit v1.2.3 From 15be9006b7b3407e81bb64dfad831f2d9317a90c Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 18:24:13 -0400 Subject: fill in template hole --- pyramid/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyramid/config.py b/pyramid/config.py index 917b689b1..60d406a62 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -995,10 +995,10 @@ class Configurator(object): raise ConfigurationError('%s is a reserved tween name' % alias) if over is INGRESS: - raise ConfigurationError('%s cannot be over INGRESS') + raise ConfigurationError('%s cannot be over INGRESS' % name) if under is MAIN: - raise ConfigurationError('%s cannot be under MAIN') + raise ConfigurationError('%s cannot be under MAIN' % name) registry = self.registry tweens = registry.queryUtility(ITweens) -- cgit v1.2.3 From 6f7ecdd034cd6fc726877fc491785aeaa478f1b4 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 19:02:02 -0400 Subject: better message --- pyramid/tweens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 90ff67267..2b0ce3927 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -54,7 +54,7 @@ class CyclicDependencyError(Exception): dependent = cycle dependees = cycles[cycle] L.append('%r sorts over %r' % (dependent, dependees)) - msg = '; '.join(L) + msg = 'Implicit tween ordering cycle:' + '; '.join(L) return msg class Tweens(object): -- cgit v1.2.3 From 01d793025197e9d3b7749f475050f55a17dfd145 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 19:03:03 -0400 Subject: reformat code --- pyramid/tweens.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 2b0ce3927..54da9b15a 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -176,15 +176,12 @@ class Tweens(object): return handler def tween_factory_name(factory): - if (hasattr(factory, '__name__') and - hasattr(factory, '__module__')): + if (hasattr(factory, '__name__') and hasattr(factory, '__module__')): # function or class - name = '.'.join([factory.__module__, - factory.__name__]) + name = '.'.join([factory.__module__, factory.__name__]) elif hasattr(factory, '__module__'): # instance - name = '.'.join([factory.__module__, - factory.__class__.__name__, + name = '.'.join([factory.__module__, factory.__class__.__name__, str(id(factory))]) else: raise ConfigurationError( -- cgit v1.2.3 From 3e3fcdf1376218a4fa6dcffec4f27a41c63d1675 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 10 Aug 2011 19:08:49 -0400 Subject: clean off exception attr from request too --- pyramid/tests/test_router.py | 11 +++++------ pyramid/tweens.py | 7 +++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 19134813f..2b00cba22 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -812,10 +812,8 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['OK']) - # ``exception`` must be attached to request even if a suitable - # exception view cannot be found - self.assertEqual(request.exception.__class__, RuntimeError) - # we clean up the exc_info after the request + # we clean up the exc_info and exception after the request + self.assertEqual(request.exception, None) self.assertEqual(request.exc_info, None) def test_call_view_raises_exception_view(self): @@ -826,7 +824,9 @@ class TestRouter(unittest.TestCase): exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=RuntimeError) - exception_view = DummyView(exception_response) + def exception_view(context, request): + self.assertEqual(request.exception.__class__, RuntimeError) + return exception_response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, @@ -835,7 +835,6 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) - self.assertEqual(view.request.exception.__class__, RuntimeError) def test_call_view_raises_super_exception_sub_exception_view(self): from pyramid.interfaces import IViewClassifier diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 54da9b15a..5ada88b24 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -36,8 +36,11 @@ def excview_tween_factory(handler, registry): raise response = view_callable(exc, request) finally: - # prevent leakage - attrs['exc_info'] = None + # prevent leakage (wrt exc_info) + if 'exc_info' in attrs: + del attrs['exc_info'] + if 'exception' in attrs: + del attrs['exception'] return response -- cgit v1.2.3