From d3e9c271ccf004effc6b8121412b7805928bce2f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 30 Sep 2012 21:57:40 -0400 Subject: An attempt at better behavior when reload cannot restart the controlled application, see issue #681 --- pyramid/scripts/pserve.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index 9fbf0729a..f89652b51 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -150,7 +150,8 @@ class PServeCommand(object): _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' - possible_subcommands = ('start', 'stop', 'restart', 'status') + possible_subcommands = ('start', 'stop', 'restart', 'status', + 'handle_reload_error') def __init__(self, argv, quiet=False): self.quiet = quiet @@ -191,6 +192,34 @@ class PServeCommand(object): install_reloader(int(self.options.reload_interval), [app_spec]) # if self.requires_config_file: # watch_file(self.args[0]) + if cmd == 'handle_reload_error': + self.out( + 'There was a reload error: your application did not ' + 'start properly when restarted by the reloader. You ' + 'will need to examine the traceback above, and fix ' + 'the issue. The process will restart after each code ' + 'change until the problem is fixed. In some ' + 'circumstances (such as when there is an ImportError ' + 'raised at module scope), changes you make to the ' + 'offending module will not cause a restart ' + 'and you will need to either change the __init__.py ' + 'of your application to force a reload. If that ' + 'does not work, you will need to restart the process ' + 'by hand.') + app_name = self.options.app_name + base = os.getcwd() + vars = self.parse_vars(restvars) + if not self._scheme_re.search(app_spec): + app_spec = 'config:' + app_spec + try: # populate sys.modules + app = self.loadapp( + app_spec, name=app_name, + relative_to=base, global_conf=vars) + except: # but ignore any exceptions + pass + while 1: + time.sleep(1) + else: return self.restart_with_reloader() @@ -526,8 +555,10 @@ class PServeCommand(object): if reloader: # Reloader always exits with code 3; but if we are # a monitor, any exit code will restart - if exit_code != 3: - return exit_code + while exit_code != 3: + handle_error_args = args + ['handle_reload_error'] + proc = subprocess.Popen(handle_error_args, env=new_environ) + exit_code = proc.wait() if self.verbose > 0: self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) -- cgit v1.2.3 From 24e14af20902f7d5c491092fc1643787fc0a802a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 30 Sep 2012 21:59:45 -0400 Subject: s/either// --- pyramid/scripts/pserve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index f89652b51..533f708fc 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -202,7 +202,7 @@ class PServeCommand(object): 'circumstances (such as when there is an ImportError ' 'raised at module scope), changes you make to the ' 'offending module will not cause a restart ' - 'and you will need to either change the __init__.py ' + 'and you will need to change the __init__.py ' 'of your application to force a reload. If that ' 'does not work, you will need to restart the process ' 'by hand.') -- cgit v1.2.3 From ccf286e6641184adb6e4bf739c88c6b109466843 Mon Sep 17 00:00:00 2001 From: "David\\ Beitey" Date: Wed, 3 Oct 2012 10:09:34 +1000 Subject: Clarify documentation for pyramid.session.check_csrf_token function --- pyramid/session.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyramid/session.py b/pyramid/session.py index 3b2834693..a5e6a8d3a 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -83,8 +83,9 @@ def signed_deserialize(serialized, secret, hmac=hmac): def check_csrf_token(request, token='csrf_token', raises=True): """ Check the CSRF token in the request's session against the value in - ``request.params.get(token)``. If ``token`` is not supplied, the string - value ``csrf_token`` will be used as the token value. If the value in + ``request.params.get(token)``. If a ``token`` keyword is not supplied + to this function, the string ``csrf_token`` will be used to look up + the token within ``request.params``. If the value in ``request.params.get(token)`` doesn't match the value supplied by ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this function will raise an :exc:`pyramid.httpexceptions.HTTPBadRequest` -- cgit v1.2.3 From fa587efc26c285cdc7e23990f947fdbd233ab53a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Oct 2012 14:23:09 -0400 Subject: fix caching of template registrations that reference macros --- pyramid/renderers.py | 31 +++++++++++++++++++------------ pyramid/tests/fixtures/withmacro.pt | 1 + pyramid/tests/test_chameleon_zpt.py | 9 +++++++-- pyramid/tests/test_renderers.py | 7 ++++++- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 1368e190e..57a61ebba 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -367,6 +367,12 @@ class JSONP(JSON): @implementer(IChameleonLookup) class ChameleonRendererLookup(object): + spec_re = re.compile( + r'(?P[\w_.:/-]+)' + r'(?:\#(?P[\w_]+))?' + r'(\.(?P.*))' + ) + def __init__(self, impl, registry): self.impl = impl self.registry = registry @@ -417,6 +423,12 @@ class ChameleonRendererLookup(object): return False return settings.get('reload_templates', False) + def _crack_spec(self, spec): + asset, macro, ext = self.spec_re.match(spec).group( + 'asset', 'defname', 'ext' + ) + return asset, macro, ext + def __call__(self, info): spec = self.get_spec(info.name, info.package) registry = info.registry @@ -436,27 +448,22 @@ class ChameleonRendererLookup(object): # spec is a package:relpath asset spec renderer = registry.queryUtility(ITemplateRenderer, name=spec) if renderer is None: - p = re.compile( - r'(?P[\w_.:/-]+)' - r'(?:\#(?P[\w_]+))?' - r'(\.(?P.*))' - ) - asset, macro, ext = p.match(spec).group( - 'asset', 'defname', 'ext' - ) - spec = '%s.%s' % (asset, ext) + asset, macro, ext = self._crack_spec(spec) + spec_without_macro = '%s.%s' % (asset, ext) try: - package_name, filename = spec.split(':', 1) + package_name, filename = spec_without_macro.split(':', 1) except ValueError: # pragma: no cover # somehow we were passed a relative pathname; this # should die package_name = caller_package(4).__name__ - filename = spec + filename = spec_without_macro abspath = pkg_resources.resource_filename(package_name, filename) if not pkg_resources.resource_exists(package_name, filename): raise ValueError( - 'Missing template asset: %s (%s)' % (spec, abspath)) + 'Missing template asset: %s (%s)' % ( + spec_without_macro, abspath) + ) renderer = self.impl(abspath, self, macro=macro) settings = info.settings if not settings.get('reload_assets'): diff --git a/pyramid/tests/fixtures/withmacro.pt b/pyramid/tests/fixtures/withmacro.pt index 8bca01e4d..6fa654645 100644 --- a/pyramid/tests/fixtures/withmacro.pt +++ b/pyramid/tests/fixtures/withmacro.pt @@ -1,4 +1,5 @@ +Outside macro Hello! diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 37538e83e..5ac57f869 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -132,8 +132,13 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): result = instance.implementation()() self.assertEqual(result, '\n Hello!\n') - - + def test_macro_notsupplied(self): + minimal = self._getTemplatePath('withmacro.pt') + lookup = DummyLookup() + instance = self._makeOne(minimal, lookup) + result = instance.implementation()() + self.assertEqual(result, + '\nOutside macro\n\n Hello!\n\n\n\n') class DummyLookup(object): auto_reload=True diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index af9188abc..cb6c364a7 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -295,6 +295,7 @@ class TestChameleonRendererLookup(unittest.TestCase): self.assertEqual(factory.kw, {'macro':None}) def test___call__spec_withmacro(self): + from pyramid.interfaces import ITemplateRenderer import os from pyramid import tests module_name = tests.__name__ @@ -302,10 +303,11 @@ class TestChameleonRendererLookup(unittest.TestCase): renderer = {} factory = DummyFactory(renderer) spec = '%s:%s' % (module_name, relpath) + reg = self.config.registry info = DummyRendererInfo({ 'name':spec, 'package':None, - 'registry':self.config.registry, + 'registry':reg, 'settings':{}, 'type':'type', }) @@ -318,6 +320,9 @@ class TestChameleonRendererLookup(unittest.TestCase): 'withmacro.pt') self.assertTrue(factory.path.startswith(path)) self.assertEqual(factory.kw, {'macro':'foo'}) + self.assertTrue( + reg.getUtility(ITemplateRenderer, name=spec) is renderer + ) def test___call__reload_assets_true(self): import pyramid.tests -- cgit v1.2.3 From 66d277a1f6cdf752c0fd3716c3ef1780421b4eb1 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Oct 2012 14:26:07 -0400 Subject: garden --- CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index e40401528..4b3dabbec 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,6 +14,13 @@ Bug Fixes 1.4 too (a registry is attached to a request passed to bootstrap or prepare). +- When registering a view configuration that named a Chameleon ZPT renderer + with a macro name in it (e.g. ``renderer='some/template#somemacro.pt``) as + well as a view configuration without a macro name it it that pointed to the + same template (e.g. ``renderer='some/template.pt'), internal caching could + confuse the two, and your code might have rendered one instead of the + other. + 1.4a2 (2012-09-27) ================== -- cgit v1.2.3 From 7d8e08ecff656fd0f525a0fd655cdf20915f5fa8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 5 Oct 2012 14:37:36 -0400 Subject: remove unintended commit --- pyramid/scripts/pserve.py | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index 533f708fc..9fbf0729a 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -150,8 +150,7 @@ class PServeCommand(object): _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' - possible_subcommands = ('start', 'stop', 'restart', 'status', - 'handle_reload_error') + possible_subcommands = ('start', 'stop', 'restart', 'status') def __init__(self, argv, quiet=False): self.quiet = quiet @@ -192,34 +191,6 @@ class PServeCommand(object): install_reloader(int(self.options.reload_interval), [app_spec]) # if self.requires_config_file: # watch_file(self.args[0]) - if cmd == 'handle_reload_error': - self.out( - 'There was a reload error: your application did not ' - 'start properly when restarted by the reloader. You ' - 'will need to examine the traceback above, and fix ' - 'the issue. The process will restart after each code ' - 'change until the problem is fixed. In some ' - 'circumstances (such as when there is an ImportError ' - 'raised at module scope), changes you make to the ' - 'offending module will not cause a restart ' - 'and you will need to change the __init__.py ' - 'of your application to force a reload. If that ' - 'does not work, you will need to restart the process ' - 'by hand.') - app_name = self.options.app_name - base = os.getcwd() - vars = self.parse_vars(restvars) - if not self._scheme_re.search(app_spec): - app_spec = 'config:' + app_spec - try: # populate sys.modules - app = self.loadapp( - app_spec, name=app_name, - relative_to=base, global_conf=vars) - except: # but ignore any exceptions - pass - while 1: - time.sleep(1) - else: return self.restart_with_reloader() @@ -555,10 +526,8 @@ class PServeCommand(object): if reloader: # Reloader always exits with code 3; but if we are # a monitor, any exit code will restart - while exit_code != 3: - handle_error_args = args + ['handle_reload_error'] - proc = subprocess.Popen(handle_error_args, env=new_environ) - exit_code = proc.wait() + if exit_code != 3: + return exit_code if self.verbose > 0: self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) -- cgit v1.2.3 From 1273d56ee5c038f447dce0525844cd3ea6c15e4d Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sun, 7 Oct 2012 02:03:47 -0400 Subject: - The Configurator ``testing_securitypolicy`` method now returns the policy object it creates. - The Configurator ``testing_securitypolicy`` method accepts two new arguments: ``remember_result`` and ``forget_result``. If supplied, these values influence the result of the policy's ``remember`` and ``forget`` methods, respectively. - The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a ``forgotten`` value on the policy (the value ``True``) when its ``forget`` method is called. - The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a ``remembered`` value on the policy, which is the value of the ``principal`` argument it's called with when its ``remember`` method is called. --- CHANGES.txt | 19 +++++++++++++++++++ pyramid/config/testing.py | 26 ++++++++++++++++++++++++-- pyramid/testing.py | 15 ++++++++++++--- pyramid/tests/test_config/test_testing.py | 24 ++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4b3dabbec..df4ada7e9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -21,6 +21,25 @@ Bug Fixes confuse the two, and your code might have rendered one instead of the other. +Features +-------- + +- The Configurator ``testing_securitypolicy`` method now returns the policy + object it creates. + +- The Configurator ``testing_securitypolicy`` method accepts two new + arguments: ``remember_result`` and ``forget_result``. If supplied, these + values influence the result of the policy's ``remember`` and ``forget`` + methods, respectively. + +- The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a + ``forgotten`` value on the policy (the value ``True``) when its ``forget`` + method is called. + +- The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a + ``remembered`` value on the policy, which is the value of the ``principal`` + argument it's called with when its ``remember`` method is called. + 1.4a2 (2012-09-27) ================== diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py index f40cf25a7..abbbffc10 100644 --- a/pyramid/config/testing.py +++ b/pyramid/config/testing.py @@ -19,7 +19,8 @@ from pyramid.config.util import action_method class TestingConfiguratorMixin(object): # testing API def testing_securitypolicy(self, userid=None, groupids=(), - permissive=True): + permissive=True, remember_result=None, + forget_result=None): """Unit/integration testing helper: Registers a pair of faux :app:`Pyramid` security policies: a :term:`authentication policy` and a :term:`authorization policy`. @@ -31,6 +32,24 @@ class TestingConfiguratorMixin(object): nonpermissive :term:`authorization policy` is registered; this policy denies all access. + ``remember_result``, if provided, should be the result returned by + the ``remember`` method of the faux authentication policy. If it is + not provided (or it is provided, and is ``None``), the default value + ``[]`` (the empty list) will be returned by ``remember``. + + .. note:: + + ``remember_result`` is new as of Pyramid 1.4. + + ``forget_result``, if provided, should be the result returned by + the ``forget`` method of the faux authentication policy. If it is + not provided (or it is provided, and is ``None``), the default value + ``[]`` (the empty list) will be returned by ``forget``. + + .. note:: + + ``forget_result`` is new as of Pyramid 1.4. + The behavior of the registered :term:`authentication policy` depends on the values provided for the ``userid`` and ``groupids`` argument. The authentication policy will return @@ -47,9 +66,12 @@ class TestingConfiguratorMixin(object): :func:`pyramid.security.principals_allowed_by_permission`. """ from pyramid.testing import DummySecurityPolicy - policy = DummySecurityPolicy(userid, groupids, permissive) + policy = DummySecurityPolicy( + userid, groupids, permissive, remember_result, forget_result + ) self.registry.registerUtility(policy, IAuthorizationPolicy) self.registry.registerUtility(policy, IAuthenticationPolicy) + return policy def testing_resources(self, resources): """Unit/integration testing helper: registers a dictionary of diff --git a/pyramid/testing.py b/pyramid/testing.py index 9e8f2bff3..cecf13469 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -53,10 +53,17 @@ class DummyRootFactory(object): class DummySecurityPolicy(object): """ A standin for both an IAuthentication and IAuthorization policy """ - def __init__(self, userid=None, groupids=(), permissive=True): + def __init__(self, userid=None, groupids=(), permissive=True, + remember_result=None, forget_result=None): self.userid = userid self.groupids = groupids self.permissive = permissive + if remember_result is None: + remember_result = [] + if forget_result is None: + forget_result = [] + self.remember_result = remember_result + self.forget_result = forget_result def authenticated_userid(self, request): return self.userid @@ -73,10 +80,12 @@ class DummySecurityPolicy(object): return effective_principals def remember(self, request, principal, **kw): - return [] + self.remembered = principal + return self.remember_result def forget(self, request): - return [] + self.forgotten = True + return self.forget_result def permits(self, context, principals, permission): return self.permissive diff --git a/pyramid/tests/test_config/test_testing.py b/pyramid/tests/test_config/test_testing.py index 6c048b46d..1089f09fc 100644 --- a/pyramid/tests/test_config/test_testing.py +++ b/pyramid/tests/test_config/test_testing.py @@ -23,6 +23,30 @@ class TestingConfiguratorMixinTests(unittest.TestCase): self.assertEqual(ut.groupids, ('group1', 'group2')) self.assertEqual(ut.permissive, False) + def test_testing_securitypolicy_remember_result(self): + from pyramid.security import remember + config = self._makeOne(autocommit=True) + pol = config.testing_securitypolicy( + 'user', ('group1', 'group2'), + permissive=False, remember_result=True) + request = DummyRequest() + request.registry = config.registry + val = remember(request, 'fred') + self.assertEqual(pol.remembered, 'fred') + self.assertEqual(val, True) + + def test_testing_securitypolicy_forget_result(self): + from pyramid.security import forget + config = self._makeOne(autocommit=True) + pol = config.testing_securitypolicy( + 'user', ('group1', 'group2'), + permissive=False, forget_result=True) + request = DummyRequest() + request.registry = config.registry + val = forget(request) + self.assertEqual(pol.forgotten, True) + self.assertEqual(val, True) + def test_testing_resources(self): from pyramid.traversal import find_resource from pyramid.interfaces import ITraverser -- cgit v1.2.3 From 7cd6b99cba30093604b3344c3cb6d235977f3ae3 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Fri, 12 Oct 2012 03:44:43 -0400 Subject: Fix indentation --- pyramid/chameleon_zpt.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index 73203a7cb..d8a8ee1be 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -18,10 +18,12 @@ class ZPTTemplateRenderer(object): @reify # avoid looking up reload_templates before manager pushed def template(self): - tf = PageTemplateFile(self.path, - auto_reload=self.lookup.auto_reload, - debug=self.lookup.debug, - translate=self.lookup.translate) + tf = PageTemplateFile( + self.path, + auto_reload=self.lookup.auto_reload, + debug=self.lookup.debug, + translate=self.lookup.translate + ) if self.macro: # render only the portion of the template included in a # define-macro named the value of self.macro -- cgit v1.2.3