diff options
| author | Michael Merickel <michael@merickel.org> | 2018-08-08 18:12:07 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-08-08 18:12:07 -0500 |
| commit | 88138fd12ef38975b66b2e4dde8472cdc7c8a26f (patch) | |
| tree | 181d53710e66db471a5cf98cf1fff0ff2919b6ce | |
| parent | f997988c09cb6e5a7c27e41872b5e724a749e96e (diff) | |
| parent | 52fde9aa164979a5c9e547826c811e9b7116ae93 (diff) | |
| download | pyramid-88138fd12ef38975b66b2e4dde8472cdc7c8a26f.tar.gz pyramid-88138fd12ef38975b66b2e4dde8472cdc7c8a26f.tar.bz2 pyramid-88138fd12ef38975b66b2e4dde8472cdc7c8a26f.zip | |
Merge pull request #3113 from mmerickel/refactor-utils
fix circular imports
26 files changed, 553 insertions, 546 deletions
diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 445d6fcd2..9d61e4d90 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -34,6 +34,7 @@ from pyramid.security import ( ) from pyramid.util import strings_differ +from pyramid.util import SimpleSerializer VALID_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9+_-]*$") @@ -797,7 +798,7 @@ class AuthTktCookieHelper(object): max_age=None, http_only=False, path="/", wild_domain=True, hashalg='md5', parent_domain=False, domain=None): - serializer = _SimpleSerializer() + serializer = SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name=cookie_name, @@ -1125,14 +1126,6 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): return self.check(username, password, request) -class _SimpleSerializer(object): - def loads(self, bstruct): - return native_(bstruct) - - def dumps(self, appstruct): - return bytes_(appstruct) - - HTTPBasicCredentials = namedtuple( 'HTTPBasicCredentials', ['username', 'password']) diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 886eec0df..f4fcf413e 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -58,12 +58,17 @@ from pyramid.settings import aslist from pyramid.threadlocal import manager from pyramid.util import ( - ActionInfo, WeakOrderedSet, - action_method, object_description, ) +from pyramid.config.util import ( + ActionInfo, + PredicateList, + action_method, + not_, +) + from pyramid.config.adapters import AdaptersConfiguratorMixin from pyramid.config.assets import AssetsConfiguratorMixin from pyramid.config.factories import FactoriesConfiguratorMixin @@ -74,7 +79,6 @@ from pyramid.config.security import SecurityConfiguratorMixin from pyramid.config.settings import SettingsConfiguratorMixin from pyramid.config.testing import TestingConfiguratorMixin from pyramid.config.tweens import TweensConfiguratorMixin -from pyramid.config.util import PredicateList, not_ from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin @@ -83,9 +87,7 @@ from pyramid.path import DottedNameResolver empty = text_('') _marker = object() -ConfigurationError = ConfigurationError # pyflakes - -not_ = not_ # pyflakes, this is an API +not_ = not_ # api PHASE0_CONFIG = PHASE0_CONFIG # api PHASE1_CONFIG = PHASE1_CONFIG # api diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index a68070134..945faa3c6 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -10,10 +10,9 @@ from pyramid.interfaces import ( IResourceURL, ) -from pyramid.config.util import ( - action_method, - takes_one_arg, - ) +from pyramid.util import takes_one_arg + +from pyramid.config.util import action_method class AdaptersConfiguratorMixin(object): diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index 6eafc1eb1..b9536df42 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -12,7 +12,7 @@ from pyramid.interfaces import ( from pyramid.exceptions import ConfigurationError from pyramid.threadlocal import get_current_registry -from pyramid.util import action_method +from pyramid.config.util import action_method class OverrideProvider(pkg_resources.DefaultProvider): def __init__(self, module): diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 202c3ef61..7a5b589cf 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -15,11 +15,11 @@ from pyramid.router import default_execution_policy from pyramid.traversal import DefaultRootFactory from pyramid.util import ( - action_method, get_callable_name, InstancePropertyHelper, ) +from pyramid.config.util import action_method class FactoriesConfiguratorMixin(object): @action_method diff --git a/pyramid/config/i18n.py b/pyramid/config/i18n.py index 9c5b7e682..5dabe2845 100644 --- a/pyramid/config/i18n.py +++ b/pyramid/config/i18n.py @@ -5,7 +5,8 @@ from pyramid.interfaces import ( from pyramid.exceptions import ConfigurationError from pyramid.path import AssetResolver -from pyramid.util import action_method + +from pyramid.config.util import action_method class I18NConfiguratorMixin(object): @action_method diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py index 5d99d6564..bda763161 100644 --- a/pyramid/config/predicates.py +++ b/pyramid/config/predicates.py @@ -1,2 +1,2 @@ import zope.deprecation -zope.deprecation.moved('pyramid.predicates', 'Pyramid 2.0') +zope.deprecation.moved('pyramid.predicates', 'Pyramid 1.10') diff --git a/pyramid/config/rendering.py b/pyramid/config/rendering.py index 68671d08e..0d55c41e8 100644 --- a/pyramid/config/rendering.py +++ b/pyramid/config/rendering.py @@ -3,8 +3,8 @@ from pyramid.interfaces import ( PHASE1_CONFIG, ) -from pyramid.util import action_method from pyramid import renderers +from pyramid.config.util import action_method DEFAULT_RENDERERS = ( ('json', renderers.json_renderer_factory), diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 20fdb2b5d..904c7bd4e 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -10,15 +10,18 @@ from pyramid.interfaces import ( ) from pyramid.exceptions import ConfigurationError -from pyramid.registry import predvalseq from pyramid.request import route_request_iface from pyramid.urldispatch import RoutesMapper -from pyramid.config.util import action_method from pyramid.util import as_sorted_tuple import pyramid.predicates +from pyramid.config.util import ( + action_method, + predvalseq, +) + class RoutesConfiguratorMixin(object): @action_method def add_route(self, diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 20b816161..c7afbcf4e 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -12,9 +12,9 @@ from pyramid.interfaces import ( from pyramid.csrf import LegacySessionCSRFStoragePolicy from pyramid.exceptions import ConfigurationError -from pyramid.util import action_method from pyramid.util import as_sorted_tuple +from pyramid.config.util import action_method class SecurityConfiguratorMixin(object): diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py index 5df726a31..1daf5cdeb 100644 --- a/pyramid/config/testing.py +++ b/pyramid/config/testing.py @@ -14,7 +14,7 @@ from pyramid.traversal import ( split_path_info, ) -from pyramid.util import action_method +from pyramid.config.util import action_method class TestingConfiguratorMixin(object): # testing API diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py index 16712ab16..8bf21cf71 100644 --- a/pyramid/config/tweens.py +++ b/pyramid/config/tweens.py @@ -15,11 +15,12 @@ from pyramid.tweens import ( EXCVIEW, ) -from pyramid.config.util import ( - action_method, +from pyramid.util import ( + is_string_or_iterable, TopologicalSorter, ) -from pyramid.util import is_string_or_iterable + +from pyramid.config.util import action_method class TweensConfiguratorMixin(object): def add_tween(self, tween_factory, under=None, over=None): diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 63f06ff9b..aedebd9e2 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,28 +1,81 @@ +import functools from hashlib import md5 -import inspect +import traceback +from zope.interface import implementer from pyramid.compat import ( bytes_, - getargspec, is_nonstr_iter - ) +) +from pyramid.interfaces import IActionInfo -from pyramid.compat import im_func from pyramid.exceptions import ConfigurationError +from pyramid.predicates import Notted from pyramid.registry import predvalseq - from pyramid.util import ( TopologicalSorter, - action_method, - ActionInfo, - ) + takes_one_arg, +) + +TopologicalSorter = TopologicalSorter # support bw-compat imports +takes_one_arg = takes_one_arg # support bw-compat imports + +@implementer(IActionInfo) +class ActionInfo(object): + def __init__(self, file, line, function, src): + self.file = file + self.line = line + self.function = function + self.src = src + + def __str__(self): + srclines = self.src.split('\n') + src = '\n'.join(' %s' % x for x in srclines) + return 'Line %s of file %s:\n%s' % (self.line, self.file, src) + +def action_method(wrapped): + """ Wrapper to provide the right conflict info report data when a method + that calls Configurator.action calls another that does the same. Not a + documented API but used by some external systems.""" + def wrapper(self, *arg, **kw): + if self._ainfo is None: + self._ainfo = [] + info = kw.pop('_info', None) + # backframes for outer decorators to actionmethods + backframes = kw.pop('_backframes', 0) + 2 + if is_nonstr_iter(info) and len(info) == 4: + # _info permitted as extract_stack tuple + info = ActionInfo(*info) + if info is None: + try: + f = traceback.extract_stack(limit=4) + + # Work around a Python 3.5 issue whereby it would insert an + # extra stack frame. This should no longer be necessary in + # Python 3.5.1 + last_frame = ActionInfo(*f[-1]) + if last_frame.function == 'extract_stack': # pragma: no cover + f.pop() + info = ActionInfo(*f[-backframes]) + except Exception: # pragma: no cover + info = ActionInfo(None, 0, '', '') + self._ainfo.append(info) + try: + result = wrapped(self, *arg, **kw) + finally: + self._ainfo.pop() + return result + + if hasattr(wrapped, '__name__'): + functools.update_wrapper(wrapper, wrapped) + wrapper.__docobj__ = wrapped + return wrapper -action_method = action_method # support bw compat imports -ActionInfo = ActionInfo # support bw compat imports MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() + class not_(object): """ @@ -61,30 +114,6 @@ class not_(object): def __init__(self, value): self.value = value -class Notted(object): - def __init__(self, predicate): - self.predicate = predicate - - def _notted_text(self, val): - # if the underlying predicate doesnt return a value, it's not really - # a predicate, it's just something pretending to be a predicate, - # so dont update the hash - if val: - val = '!' + val - return val - - def text(self): - return self._notted_text(self.predicate.text()) - - def phash(self): - return self._notted_text(self.predicate.phash()) - - def __call__(self, context, request): - result = self.predicate(context, request) - phash = self.phash() - if phash: - result = not result - return result # under = after # over = before @@ -189,52 +218,3 @@ class PredicateList(object): score = score | bit order = (MAX_ORDER - score) / (len(preds) + 1) return order, preds, phash.hexdigest() - -def takes_one_arg(callee, attr=None, argname=None): - ismethod = False - if attr is None: - attr = '__call__' - if inspect.isroutine(callee): - fn = callee - elif inspect.isclass(callee): - try: - fn = callee.__init__ - except AttributeError: - return False - ismethod = hasattr(fn, '__call__') - else: - try: - fn = getattr(callee, attr) - except AttributeError: - return False - - try: - argspec = getargspec(fn) - except TypeError: - return False - - args = argspec[0] - - if hasattr(fn, im_func) or ismethod: - # it's an instance method (or unbound method on py2) - if not args: - return False - args = args[1:] - - if not args: - return False - - if len(args) == 1: - return True - - if argname: - - defaults = argspec[3] - if defaults is None: - defaults = () - - if args[0] == argname: - if len(args) - len(defaults) == 1: - return True - - return False diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e5ebc8e07..eb002ec2d 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,3 +1,4 @@ +import functools import inspect import posixpath import operator @@ -54,10 +55,7 @@ from pyramid.httpexceptions import ( default_exceptionresponse_view, ) -from pyramid.registry import ( - predvalseq, - Deferred, - ) +from pyramid.registry import Deferred from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.static import static_view @@ -66,10 +64,7 @@ from pyramid.url import parse_url_overrides from pyramid.view import AppendSlashNotFoundViewFactory -import pyramid.util from pyramid.util import ( - viewdefaults, - action_method, as_sorted_tuple, TopologicalSorter, ) @@ -88,8 +83,10 @@ from pyramid.viewderivers import ( ) from pyramid.config.util import ( + action_method, DEFAULT_PHASH, MAX_ORDER, + predvalseq, ) urljoin = urlparse.urljoin @@ -183,6 +180,68 @@ class MultiView(object): continue raise PredicateMismatch(self.name) +def attr_wrapped_view(view, info): + accept, order, phash = (info.options.get('accept', None), + getattr(info, 'order', MAX_ORDER), + getattr(info, 'phash', DEFAULT_PHASH)) + # this is a little silly but we don't want to decorate the original + # function with attributes that indicate accept, order, and phash, + # so we use a wrapper + if ( + (accept is None) and + (order == MAX_ORDER) and + (phash == DEFAULT_PHASH) + ): + return view # defaults + def attr_view(context, request): + return view(context, request) + attr_view.__accept__ = accept + attr_view.__order__ = order + attr_view.__phash__ = phash + attr_view.__view_attr__ = info.options.get('attr') + attr_view.__permission__ = info.options.get('permission') + return attr_view + +attr_wrapped_view.options = ('accept', 'attr', 'permission') + +def predicated_view(view, info): + preds = info.predicates + if not preds: + return view + def predicate_wrapper(context, request): + for predicate in preds: + if not predicate(context, request): + view_name = getattr(view, '__name__', view) + raise PredicateMismatch( + 'predicate mismatch for view %s (%s)' % ( + view_name, predicate.text())) + return view(context, request) + def checker(context, request): + return all((predicate(context, request) for predicate in + preds)) + predicate_wrapper.__predicated__ = checker + predicate_wrapper.__predicates__ = preds + return predicate_wrapper + +def viewdefaults(wrapped): + """ Decorator for add_view-like methods which takes into account + __view_defaults__ attached to view it is passed. Not a documented API but + used by some external systems.""" + def wrapper(self, *arg, **kw): + defaults = {} + if arg: + view = arg[0] + else: + view = kw.get('view') + view = self.maybe_dotted(view) + if inspect.isclass(view): + defaults = getattr(view, '__view_defaults__', {}).copy() + if '_backframes' not in kw: + kw['_backframes'] = 1 # for action_method + defaults.update(kw) + return wrapped(self, *arg, **defaults) + return functools.wraps(wrapped)(wrapper) + class ViewsConfiguratorMixin(object): @viewdefaults @action_method @@ -1106,11 +1165,9 @@ class ViewsConfiguratorMixin(object): raise ConfigurationError('Unknown view options: %s' % (kw,)) def _apply_view_derivers(self, info): - d = pyramid.viewderivers - # These derivers are not really derivers and so have fixed order - outer_derivers = [('attr_wrapped_view', d.attr_wrapped_view), - ('predicated_view', d.predicated_view)] + outer_derivers = [('attr_wrapped_view', attr_wrapped_view), + ('predicated_view', predicated_view)] view = info.original_view derivers = self.registry.getUtility(IViewDerivers) diff --git a/pyramid/csrf.py b/pyramid/csrf.py index 7c836e5ad..b023bda5f 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -4,8 +4,6 @@ from webob.cookies import CookieProfile from zope.interface import implementer -from pyramid.authentication import _SimpleSerializer - from pyramid.compat import ( bytes_, urlparse, @@ -18,6 +16,7 @@ from pyramid.exceptions import ( from pyramid.interfaces import ICSRFStoragePolicy from pyramid.settings import aslist from pyramid.util import ( + SimpleSerializer, is_same_domain, strings_differ ) @@ -112,7 +111,7 @@ class CookieCSRFStoragePolicy(object): def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, domain=None, max_age=None, path='/'): - serializer = _SimpleSerializer() + serializer = SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name=cookie_name, secure=secure, diff --git a/pyramid/predicates.py b/pyramid/predicates.py index 3d7bb1b4b..5e54badff 100644 --- a/pyramid/predicates.py +++ b/pyramid/predicates.py @@ -12,8 +12,10 @@ from pyramid.traversal import ( ) from pyramid.urldispatch import _compile_route -from pyramid.util import object_description -from pyramid.util import as_sorted_tuple +from pyramid.util import ( + as_sorted_tuple, + object_description, +) _marker = object() @@ -298,3 +300,27 @@ class EffectivePrincipalsPredicate(object): return True return False +class Notted(object): + def __init__(self, predicate): + self.predicate = predicate + + def _notted_text(self, val): + # if the underlying predicate doesnt return a value, it's not really + # a predicate, it's just something pretending to be a predicate, + # so dont update the hash + if val: + val = '!' + val + return val + + def text(self): + return self._notted_text(self.predicate.text()) + + def phash(self): + return self._notted_text(self.predicate.phash()) + + def __call__(self, context, request): + result = self.predicate(context, request) + phash = self.phash() + if phash: + result = not result + return result diff --git a/pyramid/registry.py b/pyramid/registry.py index 7b75aeefc..a741c495e 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -2,7 +2,6 @@ import operator import threading from zope.interface import implementer - from zope.interface.registry import Components from pyramid.compat import text_ @@ -294,6 +293,5 @@ def undefer(v): class predvalseq(tuple): """ A subtype of tuple used to represent a sequence of predicate values """ - pass global_registry = Registry('global') diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index b9a4c6be4..aeb4a467b 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -1549,21 +1549,6 @@ class TestExtractHTTPBasicCredentials(unittest.TestCase): self.assertEqual(result.username, 'chrisr') self.assertEqual(result.password, 'pass') - - - -class TestSimpleSerializer(unittest.TestCase): - def _makeOne(self): - from pyramid.authentication import _SimpleSerializer - return _SimpleSerializer() - - def test_loads(self): - inst = self._makeOne() - self.assertEqual(inst.loads(b'abc'), text_('abc')) - - def test_dumps(self): - inst = self._makeOne() - self.assertEqual(inst.dumps('abc'), bytes_('abc')) class DummyContext: pass diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index ab584cc3d..76a3d703d 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -852,7 +852,7 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(config.action('discrim', kw={'a':1}), None) def test_action_autocommit_with_introspectables(self): - from pyramid.util import ActionInfo + from pyramid.config.util import ActionInfo config = self._makeOne(autocommit=True) intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index bb86a1f56..99c67e8c6 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -1,6 +1,38 @@ import unittest + from pyramid.compat import text_ +class TestActionInfo(unittest.TestCase): + def _getTargetClass(self): + from pyramid.config.util import ActionInfo + return ActionInfo + + def _makeOne(self, filename, lineno, function, linerepr): + return self._getTargetClass()(filename, lineno, function, linerepr) + + def test_class_conforms(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IActionInfo + verifyClass(IActionInfo, self._getTargetClass()) + + def test_instance_conforms(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IActionInfo + verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) + + def test_ctor(self): + inst = self._makeOne('filename', 10, 'function', 'src') + self.assertEqual(inst.file, 'filename') + self.assertEqual(inst.line, 10) + self.assertEqual(inst.function, 'function') + self.assertEqual(inst.src, 'src') + + def test___str__(self): + inst = self._makeOne('filename', 0, 'function', ' linerepr ') + self.assertEqual(str(inst), + "Line 0 of file filename:\n linerepr ") + + class TestPredicateList(unittest.TestCase): def _makeOne(self): @@ -391,220 +423,6 @@ class TestPredicateList(unittest.TestCase): self.assertEqual(predicates[1](None, request), True) self.assertEqual(predicates[2](None, request), True) - -class Test_takes_one_arg(unittest.TestCase): - def _callFUT(self, view, attr=None, argname=None): - from pyramid.config.util import takes_one_arg - return takes_one_arg(view, attr=attr, argname=argname) - - def test_requestonly_newstyle_class_no_init(self): - class foo(object): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_requestonly_newstyle_class_init_toomanyargs(self): - class foo(object): - def __init__(self, context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_requestonly_newstyle_class_init_onearg_named_request(self): - class foo(object): - def __init__(self, request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_newstyle_class_init_onearg_named_somethingelse(self): - class foo(object): - def __init__(self, req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_newstyle_class_init_defaultargs_firstname_not_request(self): - class foo(object): - def __init__(self, context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_newstyle_class_init_defaultargs_firstname_request(self): - class foo(object): - def __init__(self, request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo, argname='request')) - - def test_newstyle_class_init_firstname_request_with_secondname(self): - class foo(object): - def __init__(self, request, two): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_newstyle_class_init_noargs(self): - class foo(object): - def __init__(): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_no_init(self): - class foo: - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_toomanyargs(self): - class foo: - def __init__(self, context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_onearg_named_request(self): - class foo: - def __init__(self, request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_oldstyle_class_init_onearg_named_somethingelse(self): - class foo: - def __init__(self, req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_oldstyle_class_init_defaultargs_firstname_not_request(self): - class foo: - def __init__(self, context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_defaultargs_firstname_request(self): - class foo: - def __init__(self, request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo, argname='request'), True) - - def test_oldstyle_class_init_noargs(self): - class foo: - def __init__(): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_toomanyargs(self): - def foo(context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_with_attr_false(self): - def bar(context, request): - """ """ - def foo(context, request): - """ """ - foo.bar = bar - self.assertFalse(self._callFUT(foo, 'bar')) - - def test_function_with_attr_true(self): - def bar(context, request): - """ """ - def foo(request): - """ """ - foo.bar = bar - self.assertTrue(self._callFUT(foo, 'bar')) - - def test_function_onearg_named_request(self): - def foo(request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_function_onearg_named_somethingelse(self): - def foo(req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_function_defaultargs_firstname_not_request(self): - def foo(context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_defaultargs_firstname_request(self): - def foo(request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo, argname='request')) - - def test_function_noargs(self): - def foo(): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_instance_toomanyargs(self): - class Foo: - def __call__(self, context, request): - """ """ - foo = Foo() - self.assertFalse(self._callFUT(foo)) - - def test_instance_defaultargs_onearg_named_request(self): - class Foo: - def __call__(self, request): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo)) - - def test_instance_defaultargs_onearg_named_somethingelse(self): - class Foo: - def __call__(self, req): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo)) - - def test_instance_defaultargs_firstname_not_request(self): - class Foo: - def __call__(self, context, request=None): - """ """ - foo = Foo() - self.assertFalse(self._callFUT(foo)) - - def test_instance_defaultargs_firstname_request(self): - class Foo: - def __call__(self, request, foo=1, bar=2): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo, argname='request'), True) - - def test_instance_nocall(self): - class Foo: pass - foo = Foo() - self.assertFalse(self._callFUT(foo)) - - def test_method_onearg_named_request(self): - class Foo: - def method(self, request): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo.method)) - - def test_function_annotations(self): - def foo(bar): - """ """ - # avoid SyntaxErrors in python2, this if effectively nop - getattr(foo, '__annotations__', {}).update({'bar': 'baz'}) - self.assertTrue(self._callFUT(foo)) - -class TestNotted(unittest.TestCase): - def _makeOne(self, predicate): - from pyramid.config.util import Notted - return Notted(predicate) - - def test_it_with_phash_val(self): - pred = DummyPredicate('val') - inst = self._makeOne(pred) - self.assertEqual(inst.text(), '!val') - self.assertEqual(inst.phash(), '!val') - self.assertEqual(inst(None, None), False) - - def test_it_without_phash_val(self): - pred = DummyPredicate('') - inst = self._makeOne(pred) - self.assertEqual(inst.text(), '') - self.assertEqual(inst.phash(), '') - self.assertEqual(inst(None, None), True) - - class TestDeprecatedPredicates(unittest.TestCase): def test_it(self): import warnings @@ -613,18 +431,6 @@ class TestDeprecatedPredicates(unittest.TestCase): from pyramid.config.predicates import XHRPredicate self.assertEqual(len(w), 1) -class DummyPredicate(object): - def __init__(self, result): - self.result = result - - def text(self): - return self.result - - phash = text - - def __call__(self, context, request): - return True - class DummyCustomPredicate(object): def __init__(self): self.__text__ = 'custom predicate' @@ -636,8 +442,9 @@ class DummyCustomPredicate(object): @classmethod def classmethod_predicate_no_text(*args): pass # pragma: no cover -class Dummy: - pass +class Dummy(object): + def __init__(self, **kw): + self.__dict__.update(**kw) class DummyRequest: subpath = () @@ -652,4 +459,3 @@ class DummyRequest: class DummyConfigurator(object): def maybe_dotted(self, thing): return thing - diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 860254385..cb554a816 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -542,8 +542,8 @@ class TestViewsConfigurationMixin(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.util import DEFAULT_PHASH + from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -564,8 +564,8 @@ class TestViewsConfigurationMixin(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.util import DEFAULT_PHASH + from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView @@ -3385,6 +3385,18 @@ class Test_view_description(unittest.TestCase): self.assertEqual(result, 'function pyramid.tests.test_config.test_views.view') +class Test_viewdefaults(unittest.TestCase): + def _makeOne(self, wrapped): + from pyramid.decorator import reify + return reify(wrapped) + + def test_dunder_attrs_copied(self): + from pyramid.config.views import viewdefaults + decorator = self._makeOne(viewdefaults) + self.assertEqual(decorator.__doc__, viewdefaults.__doc__) + self.assertEqual(decorator.__name__, viewdefaults.__name__) + self.assertEqual(decorator.__module__, viewdefaults.__module__) + class DummyRegistry: utility = None diff --git a/pyramid/tests/test_decorator.py b/pyramid/tests/test_decorator.py index 0a98a512d..4b9313e06 100644 --- a/pyramid/tests/test_decorator.py +++ b/pyramid/tests/test_decorator.py @@ -21,13 +21,6 @@ class TestReify(unittest.TestCase): result = decorator.__get__(None) self.assertEqual(result, decorator) - def test_dunder_attrs_copied(self): - from pyramid.util import viewdefaults - decorator = self._makeOne(viewdefaults) - self.assertEqual(decorator.__doc__, viewdefaults.__doc__) - self.assertEqual(decorator.__name__, viewdefaults.__name__) - self.assertEqual(decorator.__module__, viewdefaults.__module__) - class Dummy(object): pass diff --git a/pyramid/tests/test_predicates.py b/pyramid/tests/test_predicates.py index 8a002c24e..da0b44708 100644 --- a/pyramid/tests/test_predicates.py +++ b/pyramid/tests/test_predicates.py @@ -514,13 +514,43 @@ class Test_EffectivePrincipalsPredicate(unittest.TestCase): context = Dummy() self.assertFalse(inst(context, request)) + +class TestNotted(unittest.TestCase): + def _makeOne(self, predicate): + from pyramid.predicates import Notted + return Notted(predicate) + + def test_it_with_phash_val(self): + pred = DummyPredicate('val') + inst = self._makeOne(pred) + self.assertEqual(inst.text(), '!val') + self.assertEqual(inst.phash(), '!val') + self.assertEqual(inst(None, None), False) + + def test_it_without_phash_val(self): + pred = DummyPredicate('') + inst = self._makeOne(pred) + self.assertEqual(inst.text(), '') + self.assertEqual(inst.phash(), '') + self.assertEqual(inst(None, None), True) + class predicate(object): def __repr__(self): return 'predicate' def __hash__(self): return 1 - + class Dummy(object): - def __init__(self, **kw): - self.__dict__.update(**kw) - + pass + +class DummyPredicate(object): + def __init__(self, result): + self.result = result + + def text(self): + return self.result + + phash = text + + def __call__(self, context, request): + return True diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 0f7671d59..a76cd2017 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -1,5 +1,9 @@ import unittest -from pyramid.compat import PY2 +from pyramid.compat import ( + PY2, + text_, + bytes_, + ) class Test_InstancePropertyHelper(unittest.TestCase): @@ -747,36 +751,6 @@ class TestSentinel(unittest.TestCase): r = repr(Sentinel('ABC')) self.assertEqual(r, 'ABC') -class TestActionInfo(unittest.TestCase): - def _getTargetClass(self): - from pyramid.util import ActionInfo - return ActionInfo - - def _makeOne(self, filename, lineno, function, linerepr): - return self._getTargetClass()(filename, lineno, function, linerepr) - - def test_class_conforms(self): - from zope.interface.verify import verifyClass - from pyramid.interfaces import IActionInfo - verifyClass(IActionInfo, self._getTargetClass()) - - def test_instance_conforms(self): - from zope.interface.verify import verifyObject - from pyramid.interfaces import IActionInfo - verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) - - def test_ctor(self): - inst = self._makeOne('filename', 10, 'function', 'src') - self.assertEqual(inst.file, 'filename') - self.assertEqual(inst.line, 10) - self.assertEqual(inst.function, 'function') - self.assertEqual(inst.src, 'src') - - def test___str__(self): - inst = self._makeOne('filename', 0, 'function', ' linerepr ') - self.assertEqual(str(inst), - "Line 0 of file filename:\n linerepr ") - class TestCallableName(unittest.TestCase): def test_valid_ascii(self): @@ -927,3 +901,211 @@ class Test_make_contextmanager(unittest.TestCase): mgr = self._callFUT(mygen) with mgr() as ctx: self.assertEqual(ctx, 'a') + + +class Test_takes_one_arg(unittest.TestCase): + def _callFUT(self, view, attr=None, argname=None): + from pyramid.util import takes_one_arg + return takes_one_arg(view, attr=attr, argname=argname) + + def test_requestonly_newstyle_class_no_init(self): + class foo(object): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_requestonly_newstyle_class_init_toomanyargs(self): + class foo(object): + def __init__(self, context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_requestonly_newstyle_class_init_onearg_named_request(self): + class foo(object): + def __init__(self, request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_newstyle_class_init_onearg_named_somethingelse(self): + class foo(object): + def __init__(self, req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_newstyle_class_init_defaultargs_firstname_not_request(self): + class foo(object): + def __init__(self, context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_newstyle_class_init_defaultargs_firstname_request(self): + class foo(object): + def __init__(self, request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo, argname='request')) + + def test_newstyle_class_init_firstname_request_with_secondname(self): + class foo(object): + def __init__(self, request, two): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_newstyle_class_init_noargs(self): + class foo(object): + def __init__(): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_no_init(self): + class foo: + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_toomanyargs(self): + class foo: + def __init__(self, context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_onearg_named_request(self): + class foo: + def __init__(self, request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_oldstyle_class_init_onearg_named_somethingelse(self): + class foo: + def __init__(self, req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_oldstyle_class_init_defaultargs_firstname_not_request(self): + class foo: + def __init__(self, context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_defaultargs_firstname_request(self): + class foo: + def __init__(self, request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo, argname='request'), True) + + def test_oldstyle_class_init_noargs(self): + class foo: + def __init__(): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_toomanyargs(self): + def foo(context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_with_attr_false(self): + def bar(context, request): + """ """ + def foo(context, request): + """ """ + foo.bar = bar + self.assertFalse(self._callFUT(foo, 'bar')) + + def test_function_with_attr_true(self): + def bar(context, request): + """ """ + def foo(request): + """ """ + foo.bar = bar + self.assertTrue(self._callFUT(foo, 'bar')) + + def test_function_onearg_named_request(self): + def foo(request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_function_onearg_named_somethingelse(self): + def foo(req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_function_defaultargs_firstname_not_request(self): + def foo(context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_defaultargs_firstname_request(self): + def foo(request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo, argname='request')) + + def test_function_noargs(self): + def foo(): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_instance_toomanyargs(self): + class Foo: + def __call__(self, context, request): + """ """ + foo = Foo() + self.assertFalse(self._callFUT(foo)) + + def test_instance_defaultargs_onearg_named_request(self): + class Foo: + def __call__(self, request): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo)) + + def test_instance_defaultargs_onearg_named_somethingelse(self): + class Foo: + def __call__(self, req): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo)) + + def test_instance_defaultargs_firstname_not_request(self): + class Foo: + def __call__(self, context, request=None): + """ """ + foo = Foo() + self.assertFalse(self._callFUT(foo)) + + def test_instance_defaultargs_firstname_request(self): + class Foo: + def __call__(self, request, foo=1, bar=2): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo, argname='request'), True) + + def test_instance_nocall(self): + class Foo: pass + foo = Foo() + self.assertFalse(self._callFUT(foo)) + + def test_method_onearg_named_request(self): + class Foo: + def method(self, request): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo.method)) + + def test_function_annotations(self): + def foo(bar): + """ """ + # avoid SyntaxErrors in python2, this if effectively nop + getattr(foo, '__annotations__', {}).update({'bar': 'baz'}) + self.assertTrue(self._callFUT(foo)) + + +class TestSimpleSerializer(unittest.TestCase): + def _makeOne(self): + from pyramid.util import SimpleSerializer + return SimpleSerializer() + + def test_loads(self): + inst = self._makeOne() + self.assertEqual(inst.loads(b'abc'), text_('abc')) + + def test_dumps(self): + inst = self._makeOne() + self.assertEqual(inst.dumps('abc'), bytes_('abc')) diff --git a/pyramid/util.py b/pyramid/util.py index 77a0f306b..6655455bf 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -6,26 +6,25 @@ try: except ImportError: # pragma: no cover compare_digest = None import inspect -import traceback import weakref -from zope.interface import implementer - from pyramid.exceptions import ( ConfigurationError, CyclicDependencyError, ) from pyramid.compat import ( + getargspec, + im_func, is_nonstr_iter, integer_types, string_types, + bytes_, text_, PY2, native_ ) -from pyramid.interfaces import IActionInfo from pyramid.path import DottedNameResolver as _DottedNameResolver _marker = object() @@ -528,76 +527,6 @@ class TopologicalSorter(object): return result -def viewdefaults(wrapped): - """ Decorator for add_view-like methods which takes into account - __view_defaults__ attached to view it is passed. Not a documented API but - used by some external systems.""" - def wrapper(self, *arg, **kw): - defaults = {} - if arg: - view = arg[0] - else: - view = kw.get('view') - view = self.maybe_dotted(view) - if inspect.isclass(view): - defaults = getattr(view, '__view_defaults__', {}).copy() - if '_backframes' not in kw: - kw['_backframes'] = 1 # for action_method - defaults.update(kw) - return wrapped(self, *arg, **defaults) - return functools.wraps(wrapped)(wrapper) - -@implementer(IActionInfo) -class ActionInfo(object): - def __init__(self, file, line, function, src): - self.file = file - self.line = line - self.function = function - self.src = src - - def __str__(self): - srclines = self.src.split('\n') - src = '\n'.join(' %s' % x for x in srclines) - return 'Line %s of file %s:\n%s' % (self.line, self.file, src) - -def action_method(wrapped): - """ Wrapper to provide the right conflict info report data when a method - that calls Configurator.action calls another that does the same. Not a - documented API but used by some external systems.""" - def wrapper(self, *arg, **kw): - if self._ainfo is None: - self._ainfo = [] - info = kw.pop('_info', None) - # backframes for outer decorators to actionmethods - backframes = kw.pop('_backframes', 0) + 2 - if is_nonstr_iter(info) and len(info) == 4: - # _info permitted as extract_stack tuple - info = ActionInfo(*info) - if info is None: - try: - f = traceback.extract_stack(limit=4) - - # Work around a Python 3.5 issue whereby it would insert an - # extra stack frame. This should no longer be necessary in - # Python 3.5.1 - last_frame = ActionInfo(*f[-1]) - if last_frame.function == 'extract_stack': # pragma: no cover - f.pop() - info = ActionInfo(*f[-backframes]) - except Exception: # pragma: no cover - info = ActionInfo(None, 0, '', '') - self._ainfo.append(info) - try: - result = wrapped(self, *arg, **kw) - finally: - self._ainfo.pop() - return result - - if hasattr(wrapped, '__name__'): - functools.update_wrapper(wrapper, wrapped) - wrapper.__docobj__ = wrapped - return wrapper - def get_callable_name(name): """ @@ -662,3 +591,61 @@ def make_contextmanager(fn): def wrapper(*a, **kw): yield fn(*a, **kw) return wrapper + + +def takes_one_arg(callee, attr=None, argname=None): + ismethod = False + if attr is None: + attr = '__call__' + if inspect.isroutine(callee): + fn = callee + elif inspect.isclass(callee): + try: + fn = callee.__init__ + except AttributeError: + return False + ismethod = hasattr(fn, '__call__') + else: + try: + fn = getattr(callee, attr) + except AttributeError: + return False + + try: + argspec = getargspec(fn) + except TypeError: + return False + + args = argspec[0] + + if hasattr(fn, im_func) or ismethod: + # it's an instance method (or unbound method on py2) + if not args: + return False + args = args[1:] + + if not args: + return False + + if len(args) == 1: + return True + + if argname: + + defaults = argspec[3] + if defaults is None: + defaults = () + + if args[0] == argname: + if len(args) - len(defaults) == 1: + return True + + return False + + +class SimpleSerializer(object): + def loads(self, bstruct): + return native_(bstruct) + + def dumps(self, appstruct): + return bytes_(appstruct) diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py index d2869b162..d914a4752 100644 --- a/pyramid/viewderivers.py +++ b/pyramid/viewderivers.py @@ -28,18 +28,14 @@ from pyramid.compat import ( is_unbound_method, ) -from pyramid.config.util import ( - DEFAULT_PHASH, - MAX_ORDER, - takes_one_arg, - ) - from pyramid.exceptions import ( ConfigurationError, - PredicateMismatch, ) from pyramid.httpexceptions import HTTPForbidden -from pyramid.util import object_description +from pyramid.util import ( + object_description, + takes_one_arg, +) from pyramid.view import render_view_to_response from pyramid import renderers @@ -353,49 +349,6 @@ def _authdebug_view(view, info): return wrapped_view -def predicated_view(view, info): - preds = info.predicates - if not preds: - return view - def predicate_wrapper(context, request): - for predicate in preds: - if not predicate(context, request): - view_name = getattr(view, '__name__', view) - raise PredicateMismatch( - 'predicate mismatch for view %s (%s)' % ( - view_name, predicate.text())) - return view(context, request) - def checker(context, request): - return all((predicate(context, request) for predicate in - preds)) - predicate_wrapper.__predicated__ = checker - predicate_wrapper.__predicates__ = preds - return predicate_wrapper - -def attr_wrapped_view(view, info): - accept, order, phash = (info.options.get('accept', None), - getattr(info, 'order', MAX_ORDER), - getattr(info, 'phash', DEFAULT_PHASH)) - # this is a little silly but we don't want to decorate the original - # function with attributes that indicate accept, order, and phash, - # so we use a wrapper - if ( - (accept is None) and - (order == MAX_ORDER) and - (phash == DEFAULT_PHASH) - ): - return view # defaults - def attr_view(context, request): - return view(context, request) - attr_view.__accept__ = accept - attr_view.__order__ = order - attr_view.__phash__ = phash - attr_view.__view_attr__ = info.options.get('attr') - attr_view.__permission__ = info.options.get('permission') - return attr_view - -attr_wrapped_view.options = ('accept', 'attr', 'permission') - def rendered_view(view, info): # one way or another this wrapper must produce a Response (unless # the renderer is a NullRendererHelper) |
