# HG changeset patch # User Thierry Florac # Date 1574879848 -3600 # Node ID fc35426857418c34a852cfcae04453b6d2070d74 # Parent bd5143e87b1d54fb5fb4234e8a66de84fa08b4ec Code cleanup diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/__init__.py --- a/src/pyams_pagelet/__init__.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/__init__.py Wed Nov 27 19:37:28 2019 +0100 @@ -10,8 +10,19 @@ # FOR A PARTICULAR PURPOSE. # +"""PyAMS_pagelet package + +This package is an update of z3c.pagelet package for use with Pyramid. + +Pagelets use :py:mod:`pyams_template` package, which participate in separation of Python view code +and their template implementation, to split an HTML view template into two parts: a content +template and a layout template. It also provides a "pagelet" ZCML directive which can be used to +register pagelets, as well as a "pagelet_config" decorator which can be used alternatively. +""" from pyramid.i18n import TranslationStringFactory + + _ = TranslationStringFactory('pyams_pagelet') diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/doctests/README.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_pagelet/doctests/README.rst Wed Nov 27 19:37:28 2019 +0100 @@ -0,0 +1,121 @@ + +===================== +pyams_pagelet package +===================== + +Let's start by creating a new template: + + >>> from pyramid.testing import setUp, tearDown + >>> from pyams_utils.request import get_annotations + >>> config = setUp() + >>> config.add_request_method(get_annotations, 'annotations', reify=True) + + >>> import os, tempfile + >>> temp_dir = tempfile.mkdtemp() + + >>> content_template = os.path.join(temp_dir, 'content-template.pt') + >>> with open(content_template, 'w') as file: + ... _ = file.write('
Base template content
') + + >>> layout_template = os.path.join(temp_dir, 'layout-template.pt') + >>> with open(layout_template, 'w') as file: + ... _ = file.write(''' + ... + ... + ...
${structure:view.render()}
+ ... + ... + ... ''') + +The templates must now be registered for a view and a request. We use the TemplateFactory directly +here from *pyams_template* package, while it may be done using a *template_config* decorator: + + >>> from zope.interface import implementer, Interface + >>> from pyramid.interfaces import IRequest + >>> from pyams_template.interfaces import IContentTemplate, ILayoutTemplate + + >>> from pyams_template.template import TemplateFactory + >>> factory = TemplateFactory(content_template, 'text/html') + >>> config.registry.registerAdapter(factory, (Interface, IRequest), IContentTemplate) + + >>> factory = TemplateFactory(layout_template, 'text/html') + >>> config.registry.registerAdapter(factory, (Interface, IRequest), ILayoutTemplate) + +Let's now create a pagelet view: + + >>> class IMyView(Interface): + ... """View marker interface""" + + >>> from pyams_pagelet.pagelet import Pagelet + >>> @implementer(IMyView) + ... class MyView(Pagelet): + ... """View class""" + + >>> from pyramid.testing import DummyRequest + >>> content = object() + >>> request = DummyRequest() + >>> view = MyView(content, request) + >>> print(view.render()) +
Base template content
+ + >>> print(view()) + 200 OK + Content-Type: text/html; charset=UTF-8 + Content-Length: 98 + + + +
Base template content
+ + + + +But the standard way of using a pagelet is by using the "pagelet:" TALES expression: + + >>> pagelet_template = os.path.join(temp_dir, 'pagelet-template.pt') + >>> with open(pagelet_template, 'w') as file: + ... _ = file.write(''' + ... + ... + ...
${structure:provider:pagelet}
+ ... + ... + ... ''') + +This template will be registered using the custom view interface: + + >>> from chameleon import PageTemplateFile + >>> from pyams_viewlet.provider import ProviderExpr + >>> PageTemplateFile.expression_types['provider'] = ProviderExpr + + >>> factory = TemplateFactory(pagelet_template, 'text/html') + >>> config.registry.registerAdapter(factory, (IMyView, IRequest), ILayoutTemplate) + + >>> try: + ... view() + ... except Exception as e: + ... print(repr(e)) + ContentProviderLookupError('pagelet...) + +This exception is raised because the pagelet is not yet registered; this should be done +automatically when *pyams_pagelet* package is included into Pyramid configuration: + + >>> from zope.contentprovider.interfaces import IContentProvider + >>> from pyams_pagelet.interfaces import IPagelet + >>> from pyams_pagelet.pagelet import PageletRenderer + >>> config.registry.registerAdapter(PageletRenderer, + ... (Interface, IRequest, IPagelet), + ... IContentProvider, name='pagelet') + >>> print(view()) + 200 OK + Content-Type: text/html; charset=UTF-8 + Content-Length: 99 + + + +
Base template content
+ + + + + >>> tearDown() diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/doctests/README.txt --- a/src/pyams_pagelet/doctests/README.txt Mon Jun 11 17:30:43 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -===================== -pyams_pagelet package -===================== diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/interfaces.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_pagelet/interfaces.py Wed Nov 27 19:37:28 2019 +0100 @@ -0,0 +1,52 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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. +# + +"""PyAMS_pagelet.interfaces module + +""" + +from pyramid.interfaces import IView +from zope.contentprovider.interfaces import IContentProvider +from zope.interface import Attribute, implementer +from zope.interface.interfaces import IObjectEvent, ObjectEvent + + +__docformat__ = 'restructuredtext' + + +class IPagelet(IView): + """Pagelet interface""" + + def update(self): + """Update the pagelet data.""" + + def render(self): + """Render the pagelet content w/o o-wrap.""" + + +class IPageletRenderer(IContentProvider): + """Render a pagelet by calling it's 'render' method""" + + +class IPageletCreatedEvent(IObjectEvent): + """Pagelet created event interface""" + + request = Attribute('The request object') + + +@implementer(IPageletCreatedEvent) +class PageletCreatedEvent(ObjectEvent): + """Pagelet created event""" + + def __init__(self, object): # pylint: disable=redefined-builtin + super(PageletCreatedEvent, self).__init__(object) + self.request = object.request diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/interfaces/__init__.py --- a/src/pyams_pagelet/interfaces/__init__.py Mon Jun 11 17:30:43 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -# -# Copyright (c) 2008-2015 Thierry Florac -# 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. -# - -__docformat__ = 'restructuredtext' - -# import standard packages - -# import interfaces -from pyramid.interfaces import IView -from zope.component.interfaces import IObjectEvent, ObjectEvent -from zope.contentprovider.interfaces import IContentProvider - -# import packages -from zope.interface import implementer, Attribute - - -class IPagelet(IView): - """Pagelet interface""" - - def update(self): - """Update the pagelet data.""" - - def render(self): - """Render the pagelet content w/o o-wrap.""" - - -class IPageletRenderer(IContentProvider): - """Render a pagelet by calling it's 'render' method""" - - -class IPageletCreatedEvent(IObjectEvent): - """Pagelet created event interface""" - - request = Attribute('The request object') - - -@implementer(IPageletCreatedEvent) -class PageletCreatedEvent(ObjectEvent): - """Pagelet created event""" - - def __init__(self, object): - super(PageletCreatedEvent, self).__init__(object) - self.request = object.request diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/metaconfigure.py --- a/src/pyams_pagelet/metaconfigure.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/metaconfigure.py Wed Nov 27 19:37:28 2019 +0100 @@ -10,32 +10,36 @@ # FOR A PARTICULAR PURPOSE. # - -# import standard packages +"""PyAMS_pagelet.metaconfigure module -# import interfaces -from pyams_pagelet.interfaces import IPagelet -from pyramid.interfaces import IRequest +This module provides handlers for ZCML directives. +""" -# import packages -from pyams_pagelet.pagelet import Pagelet from pyramid.exceptions import ConfigurationError +from pyramid.interfaces import IRequest from pyramid_zcml import with_context from zope.component import zcml from zope.component.interface import provideInterface from zope.interface import Interface, classImplements +from pyams_pagelet.interfaces import IPagelet +from pyams_pagelet.pagelet import Pagelet + def PageletDirective(_context, name, view, context=Interface, permission=None, layer=IRequest, **kwargs): + # pylint: disable=invalid-name + """Pagelet ZCML directive""" + if not view: - raise ConfigurationError("You must specify a view class.") - cdict = {} - cdict['__name__'] = name - cdict['permission'] = permission + raise ConfigurationError("You must specify a view class or interface") + cdict = { + '__name__': name, + 'permission': permission + } cdict.update(kwargs) new_class = type(view.__name__, (view, Pagelet), cdict) diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/metadirectives.py --- a/src/pyams_pagelet/metadirectives.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/metadirectives.py Wed Nov 27 19:37:28 2019 +0100 @@ -10,19 +10,19 @@ # FOR A PARTICULAR PURPOSE. # -__docformat__ = 'restructuredtext' - +"""PyAMS_paget.metadirectives module -# import standard packages +This module provides interface of ZCML directives. +""" -# import interfaces - -# import packages from zope.configuration.fields import GlobalObject from zope.interface import Interface from zope.schema import TextLine +__docformat__ = 'restructuredtext' + + class IPageletDirective(Interface): """Pagelet ZCML directive interface""" @@ -34,7 +34,7 @@ required=False) layer = GlobalObject(title="The request interface or class this pagelet is for", - description="Defaults to zope.publisher.interfaces.browser.IDefaultBrowserLayer.", + description="Defaults to pyramid.interfaces.IRequest", required=False) view = GlobalObject(title='View class', @@ -46,4 +46,5 @@ required=False) +# pylint: disable=vo-value-for-parameter IPageletDirective.setTaggedValue('keyword_arguments', True) diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/pagelet.py --- a/src/pyams_pagelet/pagelet.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/pagelet.py Wed Nov 27 19:37:28 2019 +0100 @@ -10,34 +10,36 @@ # FOR A PARTICULAR PURPOSE. # -__docformat__ = 'restructuredtext' - +"""PyAMS_pagelet.pagelet module -# import standard packages +This module provides the core pagelet implementation, and a "pagelet_config" decorator which +can be use to register pagelets instead of ZCML directives. +""" + import logging -logger = logging.getLogger('PyAMS (pagelet)') import venusian +from pyramid.httpexceptions import HTTPUnauthorized +from pyramid.interfaces import IRequest +from pyramid.response import Response +from pyramid_chameleon.interfaces import IChameleonTranslate +from zope.component import queryUtility +from zope.interface import Interface, implementer -# import interfaces from pyams_pagelet.interfaces import IPagelet, IPageletRenderer, PageletCreatedEvent from pyams_template.interfaces import IContentTemplate, ILayoutTemplate -from pyramid.interfaces import IRequest -from pyramid_chameleon.interfaces import IChameleonTranslate +from pyams_utils.adapter import adapter_config + -# import packages -from pyams_utils.adapter import adapter_config -from pyramid.httpexceptions import HTTPUnauthorized -from pyramid.response import Response -from zope.component import queryUtility -from zope.interface import implementer, Interface +__docformat__ = 'restructuredtext' +LOGGER = logging.getLogger('PyAMS (pagelet)') REDIRECT_STATUS_CODES = (301, 302, 303) @implementer(IPagelet) -class Pagelet(object): +class Pagelet: """Content provider with layout support""" template = None @@ -53,14 +55,20 @@ request.registry.notify(PageletCreatedEvent(self)) def update(self): - self.request.annotations['view'] = self + """See `zope.contentprovider.interfaces.IContentProvider`""" + annotations = getattr(self.request, 'annotations', None) + if annotations is not None: + annotations['view'] = self def render(self): + """See `zope.contentprovider.interfaces.IContentProvider`""" request = self.request - cdict = {'context': self.context, - 'request': request, - 'view': self, - 'translate': queryUtility(IChameleonTranslate)} + cdict = { + 'context': self.context, + 'request': request, + 'view': self, + 'translate': queryUtility(IChameleonTranslate) + } if self.template is None: registry = request.registry template = registry.queryMultiAdapter((self, request, self.context), @@ -68,7 +76,7 @@ if template is None: template = registry.getMultiAdapter((self, request), IContentTemplate) return template(**cdict) - return self.template(**cdict) + return self.template(**cdict) # pylint: disable=not-callable def __call__(self, **kwargs): """Call update and return layout template""" @@ -77,10 +85,12 @@ return '' request = self.request - cdict = {'context': self.context, - 'request': request, - 'view': self, - 'translate': queryUtility(IChameleonTranslate)} + cdict = { + 'context': self.context, + 'request': request, + 'view': self, + 'translate': queryUtility(IChameleonTranslate) + } cdict.update(kwargs) if self.layout is None: registry = request.registry @@ -89,11 +99,11 @@ if layout is None: layout = registry.getMultiAdapter((self, request), ILayoutTemplate) return Response(layout(**cdict)) - return Response(self.layout(**cdict)) + return Response(self.layout(**cdict)) # pylint: disable=not-callable @adapter_config(name='pagelet', context=(Interface, IRequest, IPagelet), provides=IPageletRenderer) -class PageletRenderer(object): +class PageletRenderer: """Pagelet renderer""" def __init__(self, context, request, pagelet): @@ -103,13 +113,14 @@ self.request = request def update(self): - pass + """See `zope.contentprovider.interfaces.IContentProvider`""" def render(self): + """See `zope.contentprovider.interfaces.IContentProvider`""" return self.__parent__.render() -class pagelet_config(object): +class pagelet_config: # pylint: disable=invalid-name """Function or class decorator used to declare a pagelet""" venusian = venusian # for testing injection @@ -126,33 +137,34 @@ settings = self.__dict__.copy() depth = settings.pop('_depth', 0) - def callback(context, name, ob): + def callback(context, name, obj): # pylint: disable=unused-argument + """Venusian decorator callback""" cdict = { '__name__': settings.get('name'), - '__module__': ob.__module__, + '__module__': obj.__module__, 'permission': settings.get('permission') } - new_class = type(ob.__name__, (ob, Pagelet), cdict) + new_class = type(obj.__name__, (obj, Pagelet), cdict) - config = context.config.with_package(info.module) - logger.debug('Registering pagelet view "{0}" for {1} ({2})'.format(settings.get('name'), - str(settings.get('context', Interface)), - str(new_class))) - config.registry.registerAdapter(new_class, - (settings.get('context', Interface), - settings.get('request_type', IRequest)), - IPagelet, settings.get('name')) + LOGGER.debug('Registering pagelet view "{0}" for {1} ({2})'.format( + settings.get('name'), str(settings.get('context', Interface)), str(new_class))) + config = context.config.with_package(info.module) # pylint: disable=no-member + registry = settings.get('registry') or config.registry + registry.registerAdapter(new_class, + (settings.get('context', Interface), + settings.get('request_type', IRequest)), + IPagelet, settings.get('name')) config.add_view(view=new_class, **settings) info = self.venusian.attach(wrapped, callback, category='pyams_pagelet', depth=depth + 1) - if info.scope == 'class': + if info.scope == 'class': # pylint: disable=no-member # if the decorator was attached to a method in a class, or # otherwise executed at class scope, we need to set an # 'attr' into the settings if one isn't already in there if settings.get('attr') is None: settings['attr'] = wrapped.__name__ - settings['_info'] = info.codeinfo # fbo "action_method" + settings['_info'] = info.codeinfo # pylint: disable=no-member return wrapped diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/tests/__init__.py --- a/src/pyams_pagelet/tests/__init__.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/tests/__init__.py Wed Nov 27 19:37:28 2019 +0100 @@ -1,1 +1,29 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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. +# +""" +Generic Test case for pyams_pagelet doctest +""" + +import os +import sys + +__docformat__ = 'restructuredtext' + + +def get_package_dir(value): + """Get package directory""" + + package_dir = os.path.split(value)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + return package_dir diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/tests/test_utilsdocs.py --- a/src/pyams_pagelet/tests/test_utilsdocs.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/tests/test_utilsdocs.py Wed Nov 27 19:37:28 2019 +0100 @@ -14,18 +14,20 @@ Generic Test case for pyams_pagelet doctest """ +import doctest +import os +import unittest + +from pyams_pagelet.tests import get_package_dir + + __docformat__ = 'restructuredtext' -import unittest -import doctest -import sys -import os +CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) -current_dir = os.path.dirname(__file__) - -def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): - """Returns a test suite, based on doctests found in /doctest.""" +def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): # pylint: disable=invalid-name + """Returns a test suite, based on doctests found in /doctests""" suite = [] if globs is None: globs = globals() @@ -33,15 +35,12 @@ flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE) - package_dir = os.path.split(test_dir)[0] - if package_dir not in sys.path: - sys.path.append(package_dir) - + package_dir = get_package_dir(test_dir) doctest_dir = os.path.join(package_dir, 'doctests') # filtering files on extension docs = [os.path.join(doctest_dir, doc) for doc in - os.listdir(doctest_dir) if doc.endswith('.txt')] + os.listdir(doctest_dir) if doc.endswith('.txt') or doc.endswith('.rst')] for test in docs: suite.append(doctest.DocFileSuite(test, optionflags=flags, @@ -51,10 +50,11 @@ return unittest.TestSuite(suite) + def test_suite(): """returns the test suite""" - return doc_suite(current_dir) + return doc_suite(CURRENT_DIR) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') - diff -r bd5143e87b1d -r fc3542685741 src/pyams_pagelet/tests/test_utilsdocstrings.py --- a/src/pyams_pagelet/tests/test_utilsdocstrings.py Mon Jun 11 17:30:43 2018 +0200 +++ b/src/pyams_pagelet/tests/test_utilsdocstrings.py Wed Nov 27 19:37:28 2019 +0100 @@ -14,16 +14,17 @@ Generic Test case for pyams_pagelet doc strings """ +import doctest +import os +import sys +import unittest + + __docformat__ = 'restructuredtext' -import unittest -import doctest -import sys -import os +CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) -current_dir = os.path.abspath(os.path.dirname(__file__)) - def doc_suite(test_dir, globs=None): """Returns a test suite, based on doc tests strings found in /*.py""" suite = [] @@ -43,7 +44,7 @@ docs = [doc for doc in docs if not doc.startswith('__')] for test in docs: - fd = open(os.path.join(package_dir, test)) + fd = open(os.path.join(package_dir, test)) # pylint: disable=invalid-name content = fd.read() fd.close() if '>>> ' not in content: @@ -55,9 +56,11 @@ return unittest.TestSuite(suite) + def test_suite(): """returns the test suite""" - return doc_suite(current_dir) + return doc_suite(CURRENT_DIR) + if __name__ == '__main__': unittest.main(defaultTest='test_suite')