diff options
| author | Martijn Faassen <faassen@startifact.com> | 2009-05-21 13:22:14 +0000 |
|---|---|---|
| committer | Martijn Faassen <faassen@startifact.com> | 2009-05-21 13:22:14 +0000 |
| commit | 692a92cf02abf5337d8be0eca21105635eff0b58 (patch) | |
| tree | b7609566c175079772f51799a762d5f731dd80d1 | |
| parent | 08618fe83d6697a83bec8013e76ff74b29390b15 (diff) | |
| download | zope-component-692a92cf02abf5337d8be0eca21105635eff0b58.tar.gz | |
Move over zope:view and zope:resource from zope.app.component into
zope.component.
| -rw-r--r-- | CHANGES.txt | 3 | ||||
| -rw-r--r-- | src/zope/component/meta.zcml | 12 | ||||
| -rw-r--r-- | src/zope/component/testfiles/views.py | 65 | ||||
| -rw-r--r-- | src/zope/component/tests.py | 414 | ||||
| -rw-r--r-- | src/zope/component/zcml.py | 279 |
5 files changed, 767 insertions, 6 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 94d05be..977a373 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,6 +6,9 @@ CHANGES - The HookableTests were not run by the testrunner. +- Add in zope:view and zope:resource implementations into + zope.component.zcml (dependency loaded with zope.component [zcml]). + 3.6.0 (2009-03-12) ================== diff --git a/src/zope/component/meta.zcml b/src/zope/component/meta.zcml index 2793591..3fe697a 100644 --- a/src/zope/component/meta.zcml +++ b/src/zope/component/meta.zcml @@ -28,6 +28,18 @@ handler="zope.component.zcml.utility" /> + <meta:directive + name="view" + schema="zope.component.zcml.IViewDirective" + handler="zope.component.zcml.view" + /> + + <meta:directive + name="resource" + schema="zope.component.zcml.IResourceDirective" + handler="zope.component.zcml.resource" + /> + </meta:directives> </configure> diff --git a/src/zope/component/testfiles/views.py b/src/zope/component/testfiles/views.py new file mode 100644 index 0000000..58062a1 --- /dev/null +++ b/src/zope/component/testfiles/views.py @@ -0,0 +1,65 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Views test. + +$Id$ +""" +from zope.interface import Interface, implements, directlyProvides + +class Request(object): + + def __init__(self, type): + directlyProvides(self, type) + +class IR(Interface): + pass + +class IV(Interface): + def index(): + pass + +class IC(Interface): pass + +class V1(object): + implements(IV) + + def __init__(self, context, request): + self.context = context + self.request = request + + def index(self): + return 'V1 here' + + def action(self): + return 'done' + +class VZMI(V1): + def index(self): + return 'ZMI here' + +class R1(object): + + def index(self): + return 'R1 here' + + def action(self): + return 'R done' + + def __init__(self, request): + pass + + implements(IV) + +class RZMI(R1): + pass diff --git a/src/zope/component/tests.py b/src/zope/component/tests.py index 47af4ca..4255401 100644 --- a/src/zope/component/tests.py +++ b/src/zope/component/tests.py @@ -19,6 +19,7 @@ import re import unittest import transaction import persistent +from cStringIO import StringIO from zope import interface, component from zope.interface.verify import verifyObject @@ -28,10 +29,19 @@ from zope.testing import doctest, renormalizing from zope.component.interfaces import ComponentLookupError from zope.component.interfaces import IComponentArchitecture from zope.component.interfaces import IComponentLookup -from zope.component.testing import setUp, tearDown +from zope.component.testing import setUp, tearDown, PlacelessSetup import zope.component.persistentregistry import zope.component.globalregistry +from zope.configuration.xmlconfig import XMLConfig, xmlconfig +from zope.configuration.exceptions import ConfigurationError +from zope.security.checker import ProxyFactory + +from zope.component.testfiles.adapter import A1, A2, A3 +from zope.component.testfiles.components import IContent, Content +from zope.component.testfiles.components import IApp +from zope.component.testfiles.views import Request, IC, IV, V1, R1, IR + # side effect gets component-based event dispatcher installed. # we should obviously make this more explicit import zope.component.event @@ -1042,7 +1052,6 @@ class StandaloneTests(unittest.TestCase): import subprocess import sys import os - import StringIO import tempfile import pickle @@ -1188,6 +1197,402 @@ class HookableTests(unittest.TestCase): else: self.fail('Deleted implementation') +class Ob3(object): + interface.implements(IC) + +template = """<configure + xmlns='http://namespaces.zope.org/zope' + i18n_domain='zope'> + %s + </configure>""" + + +class ResourceViewTests(PlacelessSetup, unittest.TestCase): + + def setUp(self): + super(ResourceViewTests, self).setUp() + XMLConfig('meta.zcml', zope.component)() + XMLConfig('meta.zcml', zope.security)() + + def testView(self): + ob = Ob3() + request = Request(IV) + self.assertEqual( + zope.component.queryMultiAdapter((ob, request), name=u'test'), None) + + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV"/> + ''' + )) + + self.assertEqual( + zope.component.queryMultiAdapter((ob, request), + name=u'test').__class__, + V1) + + + def testMultiView(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.adapter.A3" + for="zope.component.testfiles.views.IC + zope.component.testfiles.adapter.I1 + zope.component.testfiles.adapter.I2" + type="zope.component.testfiles.views.IV"/> + ''' + )) + + + ob = Ob3() + a1 = A1() + a2 = A2() + request = Request(IV) + view = zope.component.queryMultiAdapter((ob, a1, a2, request), + name=u'test') + self.assertEqual(view.__class__, A3) + self.assertEqual(view.context, (ob, a1, a2, request)) + + + def testMultiView_fails_w_multiple_factories(self): + self.assertRaises( + ConfigurationError, + xmlconfig, + StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.adapter.A3 + zope.component.testfiles.adapter.A2" + for="zope.component.testfiles.views.IC + zope.component.testfiles.adapter.I1 + zope.component.testfiles.adapter.I2" + type="zope.component.testfiles.views.IV"/> + ''' + ) + ) + + def testView_w_multiple_factories(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.adapter.A1 + zope.component.testfiles.adapter.A2 + zope.component.testfiles.adapter.A3 + zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV"/> + ''' + )) + + ob = Ob3() + + # The view should be a V1 around an A3, around an A2, around + # an A1, anround ob: + view = zope.component.queryMultiAdapter((ob, Request(IV)), name=u'test') + self.assertEqual(view.__class__, V1) + a3 = view.context + self.assertEqual(a3.__class__, A3) + a2 = a3.context[0] + self.assertEqual(a2.__class__, A2) + a1 = a2.context[0] + self.assertEqual(a1.__class__, A1) + self.assertEqual(a1.context[0], ob) + + def testView_fails_w_no_factories(self): + self.assertRaises(ConfigurationError, + xmlconfig, + StringIO(template % + ''' + <view name="test" + factory="" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV"/> + ''' + ), + ) + + + def testViewThatProvidesAnInterface(self): + ob = Ob3() + self.assertEqual( + zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test'), + None) + + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IR" + /> + ''' + )) + + self.assertEqual( + zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test'), + None) + + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IR" + provides="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.queryMultiAdapter((ob, Request(IR)), IV, u'test') + self.assertEqual(v.__class__, V1) + + + def testUnnamedViewThatProvidesAnInterface(self): + ob = Ob3() + self.assertEqual( + zope.component.queryMultiAdapter((ob, Request(IR)), IV), None) + + xmlconfig(StringIO(template % + ''' + <view factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IR" + /> + ''' + )) + + v = zope.component.queryMultiAdapter((ob, Request(IR)), IV) + self.assertEqual(v, None) + + xmlconfig(StringIO(template % + ''' + <view factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IR" + provides="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.queryMultiAdapter((ob, Request(IR)), IV) + self.assertEqual(v.__class__, V1) + + def testViewHavingARequiredClass(self): + xmlconfig(StringIO(template % ( + ''' + <view + for="zope.component.testfiles.components.Content" + type="zope.component.testfiles.views.IR" + factory="zope.component.testfiles.adapter.A1" + /> + ''' + ))) + + content = Content() + a1 = zope.component.getMultiAdapter((content, Request(IR))) + self.assert_(isinstance(a1, A1)) + + class MyContent: + interface.implements(IContent) + + self.assertRaises(ComponentLookupError, zope.component.getMultiAdapter, + (MyContent(), Request(IR))) + + def testInterfaceProtectedView(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + permission="zope.Public" + allowed_interface="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = ProxyFactory(zope.component.getMultiAdapter((Ob3(), Request(IV)), + name='test')) + self.assertEqual(v.index(), 'V1 here') + self.assertRaises(Exception, getattr, v, 'action') + + def testAttributeProtectedView(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + permission="zope.Public" + allowed_attributes="action" + /> + ''' + )) + + v = ProxyFactory(zope.component.getMultiAdapter((Ob3(), Request(IV)), + name='test')) + self.assertEqual(v.action(), 'done') + self.assertRaises(Exception, getattr, v, 'index') + + def testInterfaceAndAttributeProtectedView(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + permission="zope.Public" + allowed_attributes="action" + allowed_interface="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test') + self.assertEqual(v.index(), 'V1 here') + self.assertEqual(v.action(), 'done') + + def testDuplicatedInterfaceAndAttributeProtectedView(self): + xmlconfig(StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + permission="zope.Public" + allowed_attributes="action index" + allowed_interface="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.getMultiAdapter((Ob3(), Request(IV)), name='test') + self.assertEqual(v.index(), 'V1 here') + self.assertEqual(v.action(), 'done') + + def testIncompleteProtectedViewNoPermission(self): + self.assertRaises( + ConfigurationError, + xmlconfig, + StringIO(template % + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + allowed_attributes="action index" + /> + ''' + )) + + def testViewUndefinedPermission(self): + config = StringIO(template % ( + ''' + <view name="test" + factory="zope.component.testfiles.views.V1" + for="zope.component.testfiles.views.IC" + type="zope.component.testfiles.views.IV" + permission="zope.UndefinedPermission" + allowed_attributes="action index" + allowed_interface="zope.component.testfiles.views.IV" + /> + ''' + )) + self.assertRaises(ValueError, xmlconfig, config, testing=1) + + def testResource(self): + ob = Ob3() + self.assertEqual( + zope.component.queryAdapter(Request(IV), name=u'test'), None) + xmlconfig(StringIO(template % ( + ''' + <resource name="test" + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IV"/> + ''' + ))) + + self.assertEqual( + zope.component.queryAdapter(Request(IV), name=u'test').__class__, + R1) + + def testResourceThatProvidesAnInterface(self): + ob = Ob3() + self.assertEqual(zope.component.queryAdapter(Request(IR), IV, u'test'), + None) + + xmlconfig(StringIO(template % + ''' + <resource + name="test" + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IR" + /> + ''' + )) + + v = zope.component.queryAdapter(Request(IR), IV, name=u'test') + self.assertEqual(v, None) + + xmlconfig(StringIO(template % + ''' + <resource + name="test" + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IR" + provides="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.queryAdapter(Request(IR), IV, name=u'test') + self.assertEqual(v.__class__, R1) + + def testUnnamedResourceThatProvidesAnInterface(self): + ob = Ob3() + self.assertEqual(zope.component.queryAdapter(Request(IR), IV), None) + + xmlconfig(StringIO(template % + ''' + <resource + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IR" + /> + ''' + )) + + v = zope.component.queryAdapter(Request(IR), IV) + self.assertEqual(v, None) + + xmlconfig(StringIO(template % + ''' + <resource + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IR" + provides="zope.component.testfiles.views.IV" + /> + ''' + )) + + v = zope.component.queryAdapter(Request(IR), IV) + self.assertEqual(v.__class__, R1) + + def testResourceUndefinedPermission(self): + + config = StringIO(template % ( + ''' + <resource name="test" + factory="zope.component.testfiles.views.R1" + type="zope.component.testfiles.views.IV" + permission="zope.UndefinedPermission"/> + ''' + )) + self.assertRaises(ValueError, xmlconfig, config, testing=1) + def setUpRegistryTests(tests): setUp() @@ -1200,8 +1605,6 @@ def tearDownRegistryTests(tests): def clearZCML(test=None): tearDown() setUp() - - from zope.configuration.xmlconfig import XMLConfig XMLConfig('meta.zcml', component)() def test_suite(): @@ -1212,8 +1615,8 @@ def test_suite(): ]) return unittest.TestSuite(( - unittest.makeSuite(HookableTests), doctest.DocTestSuite(setUp=setUp, tearDown=tearDown), + unittest.makeSuite(HookableTests), doctest.DocTestSuite('zope.component.interface', setUp=setUp, tearDown=tearDown), doctest.DocTestSuite('zope.component.nexttesting'), @@ -1231,6 +1634,7 @@ def test_suite(): doctest.DocFileSuite('zcml.txt',checker=checker, setUp=setUp, tearDown=tearDown), unittest.makeSuite(StandaloneTests), + unittest.makeSuite(ResourceViewTests), )) if __name__ == "__main__": diff --git a/src/zope/component/zcml.py b/src/zope/component/zcml.py index 41dfaec..98ed64b 100644 --- a/src/zope/component/zcml.py +++ b/src/zope/component/zcml.py @@ -17,14 +17,17 @@ $Id$ """ __docformat__ = "reStructuredText" +import warnings import zope.component import zope.interface +import zope.schema import zope.configuration.fields +from zope.configuration.exceptions import ConfigurationError import zope.security.zcml from zope.component.interface import provideInterface from zope.proxy import ProxyBase, getProxiedObject from zope.security.proxy import Proxy -from zope.security.checker import InterfaceChecker, CheckerPublic +from zope.security.checker import InterfaceChecker, CheckerPublic, Checker from zope.security.adapter import LocatingTrustedAdapterFactory from zope.security.adapter import LocatingUntrustedAdapterFactory from zope.security.adapter import TrustedAdapterFactory @@ -458,3 +461,277 @@ def interface(_context, interface, type=None, name=''): callable = provideInterface, args = (name, interface, type) ) + +class IBasicViewInformation(zope.interface.Interface): + """This is the basic information for all views.""" + + for_ = zope.configuration.fields.Tokens( + title=_("Specifications of the objects to be viewed"), + description=_("""This should be a list of interfaces or classes + """), + required=True, + value_type=zope.configuration.fields.GlobalObject( + missing_value=object(), + ), + ) + + permission = zope.security.zcml.Permission( + title=_("Permission"), + description=_("The permission needed to use the view."), + required=False, + ) + + class_ = zope.configuration.fields.GlobalObject( + title=_("Class"), + description=_("A class that provides attributes used by the view."), + required=False, + ) + + layer = zope.configuration.fields.GlobalInterface( + title=_("The layer the view is in."), + description=_(""" + A skin is composed of layers. It is common to put skin + specific views in a layer named after the skin. If the 'layer' + attribute is not supplied, it defaults to 'default'."""), + required=False, + ) + + allowed_interface = zope.configuration.fields.Tokens( + title=_("Interface that is also allowed if user has permission."), + description=_(""" + By default, 'permission' only applies to viewing the view and + any possible sub views. By specifying this attribute, you can + make the permission also apply to everything described in the + supplied interface. + + Multiple interfaces can be provided, separated by + whitespace."""), + required=False, + value_type=zope.configuration.fields.GlobalInterface(), + ) + + allowed_attributes = zope.configuration.fields.Tokens( + title=_("View attributes that are also allowed if the user" + " has permission."), + description=_(""" + By default, 'permission' only applies to viewing the view and + any possible sub views. By specifying 'allowed_attributes', + you can make the permission also apply to the extra attributes + on the view object."""), + required=False, + value_type=zope.configuration.fields.PythonIdentifier(), + ) + +class IBasicResourceInformation(zope.interface.Interface): + """ + Basic information for resources + """ + + name = zope.schema.TextLine( + title=_("The name of the resource."), + description=_("The name shows up in URLs/paths. For example 'foo'."), + required=True, + default=u'', + ) + + provides = zope.configuration.fields.GlobalInterface( + title=_("The interface this component provides."), + description=_(""" + A view can provide an interface. This would be used for + views that support other views."""), + required=False, + default=zope.interface.Interface, + ) + + type = zope.configuration.fields.GlobalInterface( + title=_("Request type"), + required=True + ) + + +class IViewDirective(IBasicViewInformation, IBasicResourceInformation): + """Register a view for a component""" + + factory = zope.configuration.fields.Tokens( + title=_("Factory"), + required=False, + value_type=zope.configuration.fields.GlobalObject(), + ) + +def view(_context, factory, type, name, for_, layer=None, + permission=None, allowed_interface=None, allowed_attributes=None, + provides=zope.interface.Interface): + + if ((allowed_attributes or allowed_interface) + and (not permission)): + raise ConfigurationError( + "Must use name attribute with allowed_interface or " + "allowed_attributes" + ) + + if not factory: + raise ConfigurationError("No view factory specified.") + + if permission: + + checker = _checker(_context, permission, + allowed_interface, allowed_attributes) + + class ProxyView(object): + """Class to create simple proxy views.""" + + def __init__(self, factory, checker): + self.factory = factory + self.checker = checker + + def __call__(self, *objects): + return proxify(self.factory(*objects), self.checker) + + factory[-1] = ProxyView(factory[-1], checker) + + + if not for_: + raise ValueError("No for interfaces specified"); + for_ = tuple(for_) + + # Generate a single factory from multiple factories: + factories = factory + if len(factories) == 1: + factory = factories[0] + elif len(factories) < 1: + raise ValueError("No factory specified") + elif len(factories) > 1 and len(for_) > 1: + raise ValueError("Can't use multiple factories and multiple for") + else: + def factory(ob, request): + for f in factories[:-1]: + ob = f(ob) + return factories[-1](ob, request) + + # BBB 2006/02/18, to be removed after 12 months + if layer is not None: + for_ = for_ + (layer,) + warnings.warn_explicit( + "The 'layer' argument of the 'view' directive has been " + "deprecated. Use the 'type' argument instead. If you have " + "an existing 'type' argument IBrowserRequest, replace it with the " + "'layer' argument (the layer subclasses IBrowserRequest). " + "which subclasses BrowserRequest.", + DeprecationWarning, _context.info.file, _context.info.line) + else: + for_ = for_ + (type,) + + _context.action( + discriminator = ('view', for_, name, provides), + callable = handler, + args = ('registerAdapter', + factory, for_, provides, name, _context.info), + ) + if type is not None: + _context.action( + discriminator = None, + callable = provideInterface, + args = ('', type) + ) + + _context.action( + discriminator = None, + callable = provideInterface, + args = ('', provides) + ) + + if for_ is not None: + for iface in for_: + if iface is not None: + _context.action( + discriminator = None, + callable = provideInterface, + args = ('', iface) + ) + + +class IResourceDirective(IBasicComponentInformation, + IBasicResourceInformation): + """Register a resource""" + + layer = zope.configuration.fields.GlobalInterface( + title=_("The layer the resource is in."), + required=False, + ) + + allowed_interface = zope.configuration.fields.Tokens( + title=_("Interface that is also allowed if user has permission."), + required=False, + value_type=zope.configuration.fields.GlobalInterface(), + ) + + allowed_attributes = zope.configuration.fields.Tokens( + title=_("View attributes that are also allowed if user" + " has permission."), + required=False, + value_type=zope.configuration.fields.PythonIdentifier(), + ) + +def resource(_context, factory, type, name, layer=None, + permission=None, + allowed_interface=None, allowed_attributes=None, + provides=zope.interface.Interface): + + if ((allowed_attributes or allowed_interface) + and (not permission)): + raise ConfigurationError( + "Must use name attribute with allowed_interface or " + "allowed_attributes" + ) + + if permission: + checker = _checker(_context, permission, + allowed_interface, allowed_attributes) + + def proxyResource(request, factory=factory, checker=checker): + return proxify(factory(request), checker) + + factory = proxyResource + + if layer is not None: + warnings.warn_explicit( + "The 'layer' argument of the 'resource' directive has been " + "deprecated. Use the 'type' argument instead.", + DeprecationWarning, _context.info.file, _context.info.line) + type = layer + + _context.action( + discriminator = ('resource', name, type, provides), + callable = handler, + args = ('registerAdapter', + factory, (type,), provides, name, _context.info), + ) + _context.action( + discriminator = None, + callable = provideInterface, + args = (type.__module__ + '.' + type.__name__, type) + ) + _context.action( + discriminator = None, + callable = provideInterface, + args = (provides.__module__ + '.' + provides.__name__, type) + ) + +def _checker(_context, permission, allowed_interface, allowed_attributes): + if (not allowed_attributes) and (not allowed_interface): + allowed_attributes = ["__call__"] + + if permission == PublicPermission: + permission = CheckerPublic + + require={} + if allowed_attributes: + for name in allowed_attributes: + require[name] = permission + if allowed_interface: + for i in allowed_interface: + for name in i.names(all=True): + require[name] = permission + + checker = Checker(require) + return checker |
