Version 0.1.4.1
authorThierry Florac <tflorac@ulthar.net>
Wed, 27 Nov 2019 15:57:21 +0100
changeset 47 df022d00a9c4
parent 46 ed2dc23f7f99
child 48 8243580eb5c0
Version 0.1.4.1
.installed.cfg
buildout.cfg
docs/HISTORY.txt
docs/README.txt
setup.py
src/pyams_viewlet.egg-info/PKG-INFO
src/pyams_viewlet.egg-info/SOURCES.txt
src/pyams_viewlet.egg-info/requires.txt
src/pyams_viewlet/__init__.py
src/pyams_viewlet/doctests/README.rst
src/pyams_viewlet/doctests/README.txt
src/pyams_viewlet/interfaces.py
src/pyams_viewlet/interfaces/__init__.py
src/pyams_viewlet/manager.py
src/pyams_viewlet/metaconfigure.py
src/pyams_viewlet/metadirectives.py
src/pyams_viewlet/provider.py
src/pyams_viewlet/tests/__init__.py
src/pyams_viewlet/tests/test_utilsdocs.py
src/pyams_viewlet/tests/test_utilsdocstrings.py
src/pyams_viewlet/viewlet.py
--- a/.installed.cfg	Fri Jan 18 15:32:56 2019 +0100
+++ b/.installed.cfg	Wed Nov 27 15:57:21 2019 +0100
@@ -1,91 +1,88 @@
 [buildout]
 installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-template.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-form.egg-link
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/lingua.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-pagelet.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-catalog.egg-link
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-viewlet.egg-link
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-utils.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-file.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-zmi.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-skin.egg-link
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs/pyams-i18n.egg-link
-parts = package i18n pyflakes test
+parts = package i18n pyflakes pylint test
 
 [package]
-__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pyams_upgrade
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pcreate
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pserve
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pdistreport
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pviews
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/ptweens
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pshell
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/proutes
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/prequest
-__buildout_signature__ = zc.recipe.egg-25289128786a29bd5395ec7b9e3ceb3a zc.buildout-25289128786a29bd5395ec7b9e3ceb3a setuptools-25289128786a29bd5395ec7b9e3ceb3a
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/python
+__buildout_signature__ = zc.recipe.egg-fc68d03e7c9074dcb88f8edb384c0f13 zc.buildout-fc68d03e7c9074dcb88f8edb384c0f13 setuptools-fc68d03e7c9074dcb88f8edb384c0f13
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
-_e = /var/local/env/pyams/eggs
+_e = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
-eggs = chameleon
-	pyams_utils
-	pyams_viewlet
-	pyramid
-	zope.component
-	zope.configuration
-	zope.contentprovider
-	zope.interface
-	zope.location
-	zope.schema
-eggs-directory = /var/local/env/pyams/eggs
+eggs = pyams_viewlet
+eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 find-links = http://download.ztfy.org/eggs
+interpreter = python
 recipe = zc.recipe.egg
 
 [i18n]
 __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pybabel
+	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pot-create
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/polint
-	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pot-create
-__buildout_signature__ = zc.recipe.egg-25289128786a29bd5395ec7b9e3ceb3a zc.buildout-25289128786a29bd5395ec7b9e3ceb3a setuptools-25289128786a29bd5395ec7b9e3ceb3a
+__buildout_signature__ = zc.recipe.egg-fc68d03e7c9074dcb88f8edb384c0f13 zc.buildout-fc68d03e7c9074dcb88f8edb384c0f13 setuptools-fc68d03e7c9074dcb88f8edb384c0f13
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
-_e = /var/local/env/pyams/eggs
+_e = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
 eggs = babel
 	lingua
-eggs-directory = /var/local/env/pyams/eggs
+eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 find-links = http://download.ztfy.org/eggs
 recipe = zc.recipe.egg
 
 [pyflakes]
 __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pyflakes
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pyflakes
-__buildout_signature__ = zc.recipe.egg-25289128786a29bd5395ec7b9e3ceb3a zc.buildout-25289128786a29bd5395ec7b9e3ceb3a setuptools-25289128786a29bd5395ec7b9e3ceb3a
+__buildout_signature__ = zc.recipe.egg-fc68d03e7c9074dcb88f8edb384c0f13 zc.buildout-fc68d03e7c9074dcb88f8edb384c0f13 setuptools-fc68d03e7c9074dcb88f8edb384c0f13
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
-_e = /var/local/env/pyams/eggs
+_e = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
 eggs = pyflakes
-eggs-directory = /var/local/env/pyams/eggs
+eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 entry-points = pyflakes=pyflakes.scripts.pyflakes:main
 find-links = http://download.ztfy.org/eggs
 initialization = if not sys.argv[1:]: sys.argv[1:] = ["src"]
 recipe = zc.recipe.egg
 scripts = pyflakes
 
+[pylint]
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/epylint
+	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pylint
+	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/symilar
+	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pyreverse
+	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/pylint
+__buildout_signature__ = zc.recipe.egg-fc68d03e7c9074dcb88f8edb384c0f13 zc.buildout-fc68d03e7c9074dcb88f8edb384c0f13 setuptools-fc68d03e7c9074dcb88f8edb384c0f13
+_b = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
+_d = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
+_e = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
+arguments = sys.argv[1:]
+bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
+develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
+eggs = pyams_viewlet
+	pylint
+eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
+entry-points = pylint=pylint.lint:Run
+find-links = http://download.ztfy.org/eggs
+recipe = zc.recipe.egg
+
 [test]
 __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/parts/test
 	/home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin/test
-__buildout_signature__ = six-25289128786a29bd5395ec7b9e3ceb3a zc.recipe.testrunner-25289128786a29bd5395ec7b9e3ceb3a zc.recipe.egg-25289128786a29bd5395ec7b9e3ceb3a zc.buildout-25289128786a29bd5395ec7b9e3ceb3a zope.exceptions-25289128786a29bd5395ec7b9e3ceb3a zope.interface-25289128786a29bd5395ec7b9e3ceb3a zope.testrunner-25289128786a29bd5395ec7b9e3ceb3a setuptools-25289128786a29bd5395ec7b9e3ceb3a
+__buildout_signature__ = six-fc68d03e7c9074dcb88f8edb384c0f13 zc.recipe.egg-fc68d03e7c9074dcb88f8edb384c0f13 zc.recipe.testrunner-fc68d03e7c9074dcb88f8edb384c0f13 zc.buildout-fc68d03e7c9074dcb88f8edb384c0f13 zope.exceptions-fc68d03e7c9074dcb88f8edb384c0f13 zope.interface-fc68d03e7c9074dcb88f8edb384c0f13 zope.testrunner-fc68d03e7c9074dcb88f8edb384c0f13 setuptools-fc68d03e7c9074dcb88f8edb384c0f13
 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 _d = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
-_e = /var/local/env/pyams/eggs
+_e = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/bin
 develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/develop-eggs
 eggs = pyams_viewlet [test]
-eggs-directory = /var/local/env/pyams/eggs
+eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/eggs
 find-links = http://download.ztfy.org/eggs
 location = /home/tflorac/Dropbox/src/PyAMS/pyams_viewlet/parts/test
 recipe = zc.recipe.testrunner
--- a/buildout.cfg	Fri Jan 18 15:32:56 2019 +0100
+++ b/buildout.cfg	Wed Nov 27 15:57:21 2019 +0100
@@ -1,56 +1,32 @@
 [buildout]
-eggs-directory = /var/local/env/pyams/eggs
+eggs-directory = eggs
 extends = http://download.ztfy.org/pyams/pyams-dev.cfg
 find-links = http://download.ztfy.org/eggs
-
 socket-timeout = 3
 
-#allow-picked-versions = false
+versions = versions
+allow-picked-versions = false
 show-picked-versions = true
 newest = false
 
-allow-hosts =
-    bitbucket.org
-    *.python.org
-    *.sourceforge.net
-    github.com
-
-versions = versions
-
 src = src
 develop =
     .
     ../ext/lingua
-    ../pyams_catalog
-    ../pyams_file
-    ../pyams_form
-    ../pyams_i18n
-    ../pyams_pagelet
-    ../pyams_skin
     ../pyams_template
     ../pyams_utils
-    ../pyams_viewlet
-    ../pyams_zmi
 
 parts =
     package
     i18n
     pyflakes
+    pylint
     test
 
 [package]
 recipe = zc.recipe.egg
-eggs =
-    chameleon
-    pyams_utils
-    pyams_viewlet
-    pyramid
-    zope.component
-    zope.configuration
-    zope.contentprovider
-    zope.interface
-    zope.location
-    zope.schema
+eggs = pyams_viewlet
+interpreter = python
 
 [i18n]
 recipe = zc.recipe.egg
@@ -70,9 +46,17 @@
 on_install = true
 cmds = ${buildout:develop}/bin/${pyflakes:scripts}
 
+[pylint]
+recipe = zc.recipe.egg
+eggs =
+    ${package:eggs}
+    pylint
+entry-points = pylint=pylint.lint:Run
+arguments = sys.argv[1:]
+
 [test]
 recipe = zc.recipe.testrunner
 eggs = pyams_viewlet [test]
 
 [versions]
-pyams_viewlet = 0.1.7
+pyams_viewlet = 0.1.8
--- a/docs/HISTORY.txt	Fri Jan 18 15:32:56 2019 +0100
+++ b/docs/HISTORY.txt	Wed Nov 27 15:57:21 2019 +0100
@@ -1,6 +1,11 @@
 Changelog
 =========
 
+0.1.8
+-----
+ - code cleanup
+ - Gitlab-CI integration
+
 0.1.7
 -----
  - updated "provider" TALES expression to be able to provide arguments
--- a/docs/README.txt	Fri Jan 18 15:32:56 2019 +0100
+++ b/docs/README.txt	Wed Nov 27 15:57:21 2019 +0100
@@ -0,0 +1,31 @@
+=====================
+PyAMS_viewlet package
+=====================
+
+.. contents::
+
+
+What is PyAMS
+=============
+
+PyAMS (Pyramid Application Management Suite) is a small suite of packages written for applications
+and content management with the Pyramid framework.
+
+**PyAMS** is actually mainly used to manage web sites through content management applications (CMS,
+see PyAMS_content package), but many features are generic and can be used inside any kind of web
+application.
+
+
+What is PyAMS_viewlet?
+======================
+
+PyAMS_viewlet is a package which defines components called "content providers"; these content
+providers are named adapters which can be used inside Chameleon templates.
+
+PyAMS_viewlet is an adaptation of zope.viewlet package to be used inside Pyramid.
+
+
+How to use pyams_viewlet?
+=========================
+
+A whole set of PyAMS documentation is available on `ReadTheDocs <https://pyams.readthedocs.io>`_
--- a/setup.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/setup.py	Wed Nov 27 15:57:21 2019 +0100
@@ -25,14 +25,14 @@
 README = os.path.join(DOCS, 'README.txt')
 HISTORY = os.path.join(DOCS, 'HISTORY.txt')
 
-version = '0.1.7'
+version = '0.1.8'
 long_description = open(README).read() + '\n\n' + open(HISTORY).read()
 
 tests_require = []
 
 setup(name='pyams_viewlet',
       version=version,
-      description="PyAMS base viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid",
+      description="PyAMS viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid",
       long_description=long_description,
       classifiers=[
           "License :: OSI Approved :: Zope Public License",
@@ -60,8 +60,11 @@
           'setuptools',
           # -*- Extra requirements: -*-
           'chameleon',
+          'pyams_template',
           'pyams_utils',
           'pyramid',
+          'pyramid_zope_request',
+          'venusian',
           'zope.component',
           'zope.configuration',
           'zope.contentprovider',
--- a/src/pyams_viewlet.egg-info/PKG-INFO	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet.egg-info/PKG-INFO	Wed Nov 27 15:57:21 2019 +0100
@@ -1,16 +1,52 @@
 Metadata-Version: 2.1
 Name: pyams-viewlet
-Version: 0.1.7
-Summary: PyAMS base viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid
+Version: 0.1.8
+Summary: PyAMS viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid
 Home-page: http://hg.ztfy.org/pyams/pyams_viewlet
 Author: Thierry Florac
 Author-email: tflorac@ulthar.net
 License: ZPL
-Description: 
+Description: =====================
+        PyAMS_viewlet package
+        =====================
+        
+        .. contents::
+        
+        
+        What is PyAMS
+        =============
+        
+        PyAMS (Pyramid Application Management Suite) is a small suite of packages written for applications
+        and content management with the Pyramid framework.
+        
+        **PyAMS** is actually mainly used to manage web sites through content management applications (CMS,
+        see PyAMS_content package), but many features are generic and can be used inside any kind of web
+        application.
+        
+        
+        What is PyAMS_viewlet?
+        ======================
+        
+        PyAMS_viewlet is a package which defines components called "content providers"; these content
+        providers are named adapters which can be used inside Chameleon templates.
+        
+        PyAMS_viewlet is an adaptation of zope.viewlet package to be used inside Pyramid.
+        
+        
+        How to use pyams_viewlet?
+        =========================
+        
+        A whole set of PyAMS documentation is available on `ReadTheDocs <https://pyams.readthedocs.io>`_
+        
         
         Changelog
         =========
         
+        0.1.8
+        -----
+         - code cleanup
+         - Gitlab-CI integration
+        
         0.1.7
         -----
          - updated "provider" TALES expression to be able to provide arguments
--- a/src/pyams_viewlet.egg-info/SOURCES.txt	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet.egg-info/SOURCES.txt	Wed Nov 27 15:57:21 2019 +0100
@@ -4,6 +4,7 @@
 docs/README.txt
 src/pyams_viewlet/__init__.py
 src/pyams_viewlet/configure.zcml
+src/pyams_viewlet/interfaces.py
 src/pyams_viewlet/manager.py
 src/pyams_viewlet/meta.zcml
 src/pyams_viewlet/metaconfigure.py
@@ -18,8 +19,7 @@
 src/pyams_viewlet.egg-info/not-zip-safe
 src/pyams_viewlet.egg-info/requires.txt
 src/pyams_viewlet.egg-info/top_level.txt
-src/pyams_viewlet/doctests/README.txt
-src/pyams_viewlet/interfaces/__init__.py
+src/pyams_viewlet/doctests/README.rst
 src/pyams_viewlet/tests/__init__.py
 src/pyams_viewlet/tests/test_utilsdocs.py
 src/pyams_viewlet/tests/test_utilsdocstrings.py
\ No newline at end of file
--- a/src/pyams_viewlet.egg-info/requires.txt	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet.egg-info/requires.txt	Wed Nov 27 15:57:21 2019 +0100
@@ -1,7 +1,10 @@
 setuptools
 chameleon
+pyams_template
 pyams_utils
 pyramid
+pyramid_zope_request
+venusian
 zope.component
 zope.configuration
 zope.contentprovider
--- a/src/pyams_viewlet/__init__.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/__init__.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,18 +10,26 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_viewlet package
+
+Viewlets provide a generic framework for building pluggable user interfaces.
+
+Generally speaking, viewlets managers are a special type of content providers, which are used
+inside a page layout to define "regions" into which other content providers called "viewlets"
+can be plugged.
+
+By using this approach, developers can extend PyAMS features by registering new components
+which will integrate easilly into default PyAMS layout; these viewlets are registered to be
+plugged into a specific viewlet manager, which "owns" its viewlets and is defined by a
+specific interface.
+"""
+
+from chameleon import PageTemplateFile
+from pyramid.i18n import TranslationStringFactory
+
+from pyams_viewlet.provider import ProviderExpr
 
 
-# import standard library
-
-# import interfaces
-
-# import packages
-from chameleon import PageTemplateFile
-from pyams_viewlet.provider import ProviderExpr
-
-from pyramid.i18n import TranslationStringFactory
 _ = TranslationStringFactory('pyams_viewlet')
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_viewlet/doctests/README.rst	Wed Nov 27 15:57:21 2019 +0100
@@ -0,0 +1,76 @@
+=====================
+pyams_viewlet package
+=====================
+
+These doctests are based on zope.viewlet doctests.
+
+In this implementation of viewlets, we first have to define *viewlet managers*, which are special
+content providers which manage a special type of content providers called *viewlets*. Every
+viewlets manager handles the viewlets registered for it:
+
+    >>> from pyramid.testing import setUp, tearDown
+    >>> config = setUp()
+
+    >>> from pyams_viewlet.interfaces import IViewletManager
+
+    >>> class ILeftColumn(IViewletManager):
+    ...     """Left column viewlet manager"""
+
+We can then create a viewlet manager factory using this interface:
+
+    >>> from pyams_viewlet.manager import ViewletManagerFactory
+    >>> LeftColumn = ViewletManagerFactory('left-column', ILeftColumn)
+
+Having the factory, we can instantiate it:
+
+    >>> from zope.interface import implementer, Interface
+    >>> @implementer(Interface)
+    ... class Content:
+    ...     """Content class"""
+    >>> content = Content()
+
+    >>> from pyramid.interfaces import IView, IRequest
+    >>> from pyramid.testing import DummyRequest
+    >>> request = DummyRequest()
+
+    >>> @implementer(IView)
+    ... class View:
+    ...     def __init__(self, context, request):
+    ...         self.context = context
+    ...         self.request = request
+    >>> view = View(content, request)
+
+    >>> left_column = LeftColumn(content, request, view)
+
+Actually, viewlet manager doesn't render anything:
+
+    >>> left_column.update()
+    >>> left_column.render()
+    ''
+
+We have to create and register viewlets for the manager:
+
+    >>> from pyams_viewlet.interfaces import IViewlet
+    >>> from pyams_viewlet.viewlet import EmptyViewlet
+    >>> class TextBox(EmptyViewlet):
+    ...     def render(self):
+    ...         return '<div class="text">Text box!</div>'
+    ...     def __repr__(self):
+    ...         return '<TextBox object at %x>' % id(self)
+
+    >>> config.registry.registerAdapter(TextBox,
+    ...                                 (Interface, IRequest, IView, ILeftColumn),
+    ...                                 IViewlet, name='text-box')
+
+    >>> left_column.render()
+    ''
+
+Viewlet managers are memoized on rendering, so we have to reset it's state if we want to
+render it another time:
+
+    >>> left_column.reset()
+    >>> left_column.update()
+    >>> left_column.render()
+    '<div class="text">Text box!</div>'
+
+    >>> tearDown()
--- a/src/pyams_viewlet/doctests/README.txt	Fri Jan 18 15:32:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-==================
-ZTFY.utils package
-==================
-
-Introduction
-------------
-
-This package is composed of a set of utility functions, which in complement with zope.app.zapi
-package can make Zope management easier.
-
-
-Unicode functions
------------------
-
-While working with extended characters sets containing accentuated characters, it's necessary to
-conversing strings to UTF8 so that they can be used without any conversion problem.
-
-    >>> from ztfy.utils import unicode
-
-'translateString' is a utility function which can be used, for example, to generate an object's id
-without space and with accentuated characters converted to their unaccentuated version:
-
-    >>> sample = 'Mon titre accentué'
-    >>> unicode.translateString(sample)
-    u'mon titre accentue'
-
-Results are lower-cased by default ; this can be avoided be setting the 'forceLower' parameter
-to False:
-
-    >>> unicode.translateString(sample, forceLower=False)
-    u'Mon titre accentue'
-
-If input string can contain 'slashes' (/) or 'backslashes' (\), they are normally removed ; 
-by using the 'escapeSlashes' parameter, the input string is splitted and only the last element is
-returned ; this is handy to handle filenames on Windows platform:
-
-    >>> sample = 'Autre / chaîne / accentuée'
-    >>> unicode.translateString(sample)
-    u'autre chaine accentuee'
-    >>> unicode.translateString(sample, escapeSlashes=True)
-    u'accentuee'
-    >>> sample = 'C:\\Program Files\\My Application\\test.txt'
-    >>> unicode.translateString(sample)
-    u'cprogram filesmy applicationtest.txt'
-    >>> unicode.translateString(sample, escapeSlashes=True)
-    u'test.txt'
-
-To remove remaining spaces or convert them to another character, you can use the "spaces" parameter
-which can contain any string to be used instead of initial spaces:
-
-    >>> sample = 'C:\\Program Files\\My Application\\test.txt'
-    >>> unicode.translateString(sample, spaces=' ')
-    u'cprogram filesmy applicationtest.txt'
-    >>> unicode.translateString(sample, spaces='-')
-    u'cprogram-filesmy-applicationtest.txt'
-
-Spaces replacement is made in the last step, so using it with "escapeSlashes" parameter only affects
-the final result:
-
-    >>> unicode.translateString(sample, escapeSlashes=True, spaces='-')
-    u'test.txt'
-
-Unicode module also provides encoding and decoding functions:
-
-    >>> var = 'Chaîne accentuée'
-    >>> unicode.decode(var)
-    u'Cha\xeene accentu\xe9e'
-    >>> unicode.encode(unicode.decode(var)) == var
-    True
-
-    >>> utf = u'Cha\xeene accentu\xe9e'
-    >>> unicode.encode(utf)
-    'Cha\xc3\xaene accentu\xc3\xa9e'
-    >>> unicode.decode(unicode.encode(utf)) == utf
-    True
-
-
-Dates functions
----------------
-
-Dates functions are used to convert dates from/to string representation:
-
-    >>> from datetime import datetime
-    >>> from ztfy.utils import date
-    >>> now = datetime.fromtimestamp(1205000000)
-    >>> now
-    datetime.datetime(2008, 3, 8, 19, 13, 20)
-
-You can get an unicode representation of a date in ASCII format using 'unidate' fonction ; date is
-converted to GMT:
-
-    >>> udate = date.unidate(now)
-    >>> udate
-    u'2008-03-08T19:13:20+00:00'
-
-'parsedate' can be used to convert ASCII format into datetime:
-
-    >>> ddate = date.parsedate(udate)
-    >>> ddate
-    datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
-
-'datetodatetime' can be used to convert a 'date' type to a 'datetime' value ; if a 'datetime' value
-is used as argument, it is returned 'as is':
-
-    >>> ddate.date()
-    datetime.date(2008, 3, 8)
-    >>> date.datetodatetime(ddate)
-    datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
-    >>> date.datetodatetime(ddate.date())
-    datetime.datetime(2008, 3, 8, 0, 0)
-
-
-Timezones handling
-------------------
-
-Timezones handling game me headaches at first. I finally concluded that the best way (for me !) to handle
-TZ data was to store every datetime value in GMT timezone.
-As far as I know, there is no easy way to know the user's timezone from his request settings. So you can:
-- store this timezone in user's profile,
-- define a static server's timezone
-- create and register a ServerTimezoneUtility to handle server default timezone.
-
-My current default user's timezone is set to 'Europe/Paris' ; you should probably update this setting in
-'timezone.py' if you are located elsewhere.
-
-    >>> from ztfy.utils import timezone
-    >>> timezone.tztime(ddate)
-    datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
-
-'gmtime' function can be used to convert a datetime to GMT:
-
-    >>> timezone.gmtime(now)
-    datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_viewlet/interfaces.py	Wed Nov 27 15:57:21 2019 +0100
@@ -0,0 +1,66 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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_viewlet.interfaces module
+
+The module defines viewlet and viewlets manager interfaces.
+"""
+
+from zope.contentprovider.interfaces import IContentProvider
+from zope.interface import Attribute
+from zope.interface.common.mapping import IReadMapping
+
+
+__docformat__ = 'restructuredtext'
+
+
+class IViewlet(IContentProvider):
+    """A content provider that is managed by another content provider, known
+    as viewlet manager.
+
+    Note that you *cannot* call viewlets directly as a provider, i.e. through
+    the TALES ``provider`` expression, since it always has to know its manager.
+    """
+
+    manager = Attribute("""The Viewlet Manager
+
+                        The viewlet manager for which the viewlet is registered. The viewlet
+                        manager will contain any additional data that was provided by the
+                        view.
+                        """)
+
+
+class IViewletManager(IContentProvider, IReadMapping):
+    """A component that provides access to the content providers.
+
+    The viewlet manager's responsibilities are:
+
+      (1) Aggregation of all viewlets registered for the manager.
+
+      (2) Apply a set of filters to determine the availability of the
+          viewlets.
+
+      (3) Sort the viewlets based on some implemented policy.
+
+      (4) Provide an environment in which the viewlets are rendered.
+
+      (5) Render itself by rendering the HTML content of the viewlets.
+    """
+
+    def filter(self, viewlets):
+        """Filter manager viewlets"""
+
+    def sort(self, viewlets):
+        """Sort manager viewlets"""
+
+    def reset(self):
+        """Reset manager status; this can be required if the manager between renderings"""
--- a/src/pyams_viewlet/interfaces/__init__.py	Fri Jan 18 15:32:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# 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 library
-
-# import interfaces
-from zope.contentprovider.interfaces import IContentProvider
-from zope.interface.common.mapping import IReadMapping
-
-# import packages
-from zope.interface import Attribute
-
-
-class IViewlet(IContentProvider):
-    """A content provider that is managed by another content provider, known
-    as viewlet manager.
-
-    Note that you *cannot* call viewlets directly as a provider, i.e. through
-    the TALES ``provider`` expression, since it always has to know its manager.
-    """
-
-    manager = Attribute("""The Viewlet Manager
-
-                        The viewlet manager for which the viewlet is registered. The viewlet
-                        manager will contain any additional data that was provided by the
-                        view, for example the TAL namespace attributes.
-                        """)
-
-
-class IViewletManager(IContentProvider, IReadMapping):
-    """A component that provides access to the content providers.
-
-    The viewlet manager's responsibilities are:
-
-      (1) Aggregation of all viewlets registered for the manager.
-
-      (2) Apply a set of filters to determine the availability of the
-          viewlets.
-
-      (3) Sort the viewlets based on some implemented policy.
-
-      (4) Provide an environment in which the viewlets are rendered.
-
-      (5) Render itself containing the HTML content of the viewlets.
-    """
--- a/src/pyams_viewlet/manager.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/manager.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_viewlet.manager module
+
+This module defines the viewlet manager, as weel as a "viewletmanager_config" decorator
+which can be used instead of ZCML to declare a viewlets manager.
+"""
 
 import logging
 
@@ -19,9 +23,9 @@
 from pyramid.httpexceptions import HTTPUnauthorized
 from pyramid.interfaces import IRequest, IView
 from pyramid.threadlocal import get_current_registry
-from zope.component.interfaces import ComponentLookupError
 from zope.contentprovider.interfaces import BeforeUpdateEvent
 from zope.interface import Interface, classImplements, implementer
+from zope.interface.interfaces import ComponentLookupError
 from zope.location.interfaces import ILocation
 
 from pyams_template.template import get_view_template
@@ -29,19 +33,26 @@
 from pyams_viewlet.interfaces import IViewlet, IViewletManager
 
 
-logger = logging.getLogger('PyAMS (viewlet)')
+__docformat__ = 'restructuredtext'
+
+
+LOGGER = logging.getLogger('PyAMS (viewlet)')
 
 
 @implementer(IViewletManager)
-class ViewletManager(object):
+class ViewletManager:
     """The Viewlet Manager base
 
-    A generic manager class which can be instantiated
+    A generic manager class which can be instantiated.
+
+    A viewlet manager can be used as mapping and can get to a given viewlet by it's name.
     """
 
     permission = None
     template = None
 
+    viewlets = None
+
     def __init__(self, context, request, view):
         self.__updated = False
         self.__parent__ = view
@@ -61,8 +72,10 @@
 
         # If the viewlet cannot be accessed, then raise an
         # unauthorized error
-        if viewlet.permission and not self.request.has_permission(viewlet.permission, context=self.context):
-            raise HTTPUnauthorized('You are not authorized to access the provider called `%s`.' % name)
+        if viewlet.permission and not self.request.has_permission(viewlet.permission,
+                                                                  context=self.context):
+            raise HTTPUnauthorized('You are not authorized to access the '
+                                   'provider called `%s`.' % name)
 
         # Return the viewlet.
         return viewlet
@@ -79,38 +92,31 @@
         return bool(self.get(name, False))
 
     def filter(self, viewlets):
-        """Sort out all content providers
+        """Filter out all content providers
 
-        ``viewlets`` is a list of tuples of the form (name, viewlet).
+        :param viewlets: list of viewlets, each element being a tuple of (name, viewlet) form
+
+        Default implementation is filtering out viewlets for which a permission which is not
+        granted to the current principal is defined.
         """
-        # Only return viewlets accessible to the principal
         request = self.request
-        return [(name, viewlet) for name, viewlet in viewlets
-                if (not viewlet.permission) or request.has_permission(viewlet.permission, context=self.context)]
 
-    def sort(self, viewlets):
+        def _filter(viewlet):
+            """Filter viewlet based on permission"""
+            _, viewlet = viewlet
+            return (not viewlet.permission) or request.has_permission(viewlet.permission,
+                                                                      context=self.context)
+
+        return filter(_filter, viewlets)
+
+    def sort(self, viewlets):  # pylint: disable=no-self-use
         """Sort the viewlets.
 
-        ``viewlets`` is a list of tuples of the form (name, viewlet).
-        """
-        # By default, we are not sorting by viewlet name.
-        return sorted(viewlets, key=lambda x: x[0])
+        :param viewlets: list of viewlets, each element being a tuple of (name, viewlet) form
 
-    def update(self):
-        """See zope.contentprovider.interfaces.IContentProvider"""
-        # check permission
-        if self.permission and not self.request.has_permission(self.permission, context=self.context):
-            return
-        # get the viewlets from now on
-        self.viewlets = []
-        append = self.viewlets.append
-        for name, viewlet in self._get_viewlets():
-            if ILocation.providedBy(viewlet):
-                viewlet.__name__ = name
-            append(viewlet)
-        # and update them...
-        self._update_viewlets()
-        self.__updated = True
+        Default implementation is sorting viewlets by name
+        """
+        return sorted(viewlets, key=lambda x: x[0])
 
     def _get_viewlets(self):
         """Find all content providers for the region"""
@@ -128,19 +134,40 @@
             registry.notify(BeforeUpdateEvent(viewlet, self.request))
             viewlet.update()
 
+    def update(self):
+        """See :py:class:`zope.contentprovider.interfaces.IContentProvider`"""
+        # check permission
+        if self.permission and not self.request.has_permission(self.permission,
+                                                               context=self.context):
+            return
+        # get the viewlets from now on
+        self.viewlets = []
+        append = self.viewlets.append
+        for name, viewlet in self._get_viewlets():
+            if ILocation.providedBy(viewlet):
+                viewlet.__name__ = name
+            append(viewlet)
+        # and update them...
+        self._update_viewlets()
+        self.__updated = True
+
     def render(self):
-        """See zope.contentprovider.interfaces.IContentProvider"""
+        """See :py:class:`zope.contentprovider.interfaces.IContentProvider`"""
         # Check for previous update
         if not (self.__updated and self.viewlets):
             return ''
         # Now render the view
         if self.template:
-            return self.template(viewlets=self.viewlets)
-        else:
-            return '\n'.join([viewlet.render() for viewlet in self.viewlets])
+            return self.template(viewlets=self.viewlets)  # pylint: disable=not-callable
+        return '\n'.join([viewlet.render() for viewlet in self.viewlets])
+
+    def reset(self):
+        """Reset viewlet manager status"""
+        self.viewlets = None
+        self.__updated = False
 
 
-def ViewletManagerFactory(name, interface, bases=(), cdict=None):
+def ViewletManagerFactory(name, interface, bases=(), cdict=None):  # pylint: disable=invalid-name
     """Viewlet manager factory"""
 
     attr_dict = {'__name__': name}
@@ -149,17 +176,19 @@
     if ViewletManager not in bases:
         # Make sure that we do not get a default viewlet manager mixin, if the
         # provided base is already a full viewlet manager implementation.
+        # pylint: disable=no-value-for-parameter
         if not (len(bases) == 1 and IViewletManager.implementedBy(bases[0])):
             bases = bases + (ViewletManager,)
 
-    viewlet_manager_class = type('<ViewletManager providing %s>' % interface.getName(), bases, attr_dict)
+    viewlet_manager_class = type('<ViewletManager providing %s>' % interface.getName(),
+                                 bases, attr_dict)
     classImplements(viewlet_manager_class, interface)
     return viewlet_manager_class
 
 
 def get_weight(item):
     """Get sort weight of a given viewlet"""
-    name, viewlet = item
+    _, viewlet = item
     try:
         return int(viewlet.weight)
     except (TypeError, AttributeError):
@@ -168,7 +197,7 @@
 
 def get_label(item, request=None):
     """Get sort label of a given viewlet"""
-    name, viewlet = item
+    _, viewlet = item
     try:
         if request is None:
             request = check_request()
@@ -192,48 +221,51 @@
         return sorted(viewlets, key=lambda x: get_weight_and_label(x, request=self.request))
 
 
-def is_available(viewlet):
-    try:
-        return ((not viewlet.permission) or
-                viewlet.request.has_permission(viewlet.permission, context=viewlet.context)) and \
-               viewlet.available
-    except AttributeError:
-        return True
-
-
 class ConditionalViewletManager(WeightOrderedViewletManager):
     """Conditional weight ordered viewlet managers"""
 
     def filter(self, viewlets):
-        """Sort out all viewlets which are explicit not available
+        """Sort out all viewlets which are explicitly not available
+
+        Viewlets shoud have a boolean "available" attribute to specify if they are available
+        or not.
+        """
 
-        ``viewlets`` is a list of tuples of the form (name, viewlet).
-        """
-        return [(name, viewlet) for name, viewlet in viewlets if is_available(viewlet)]
+        def is_available(viewlet):
+            _, viewlet = viewlet
+            try:
+                return ((not viewlet.permission) or
+                        viewlet.request.has_permission(viewlet.permission,
+                                                       context=viewlet.context)) and \
+                       viewlet.available
+            except AttributeError:
+                return True
+
+        return filter(is_available, viewlets)
 
 
-class TemplateBasedViewletManager(object):
+class TemplateBasedViewletManager:
     """Template based viewlet manager mixin class"""
 
     template = get_view_template()
 
 
-class viewletmanager_config(object):
+class viewletmanager_config:  # pylint: disable=invalid-name
     """Class or interface decorator used to declare a viewlet manager
 
     You can provide same arguments as in 'viewletManager' ZCML directive:
-    @name = name of the viewlet; may be unique for a given viewlet manager
-    @view = the view class or interface for which viewlet is displayed
-    @for_ = the context class or interface for which viewlet is displayed
-    @permission = name of a permission required to display the viewlet
-    @layer = request interface required to display the viewlet
-    @class_ = the class handling the viewlet manager; if the decorator is applied
-      on an interface and if this argument is not provided, the viewlet manager
-      will be handled by a default ViewletManager class
-    @provides = an interface the viewlet manager provides; if the decorator is
-      applied on an Interface, this will be the decorated interface; if the
-      decorated is applied on a class and if this argument is not specified,
-      the manager will provide IViewletManager interface.
+    :param name: name of the viewlet; may be unique for a given viewlet manager
+    :param view: the view class or interface for which viewlet is displayed
+    :param for_: the context class or interface for which viewlet is displayed
+    :param permission: name of a permission required to display the viewlet
+    :param layer: request interface required to display the viewlet
+    :param class_: the class handling the viewlet manager; if the decorator is applied
+        on an interface and if this argument is not provided, the viewlet manager
+        will be handled by a default ViewletManager class
+    :param provides: an interface the viewlet manager provides; if the decorator is
+        applied on an Interface, this will be the decorated interface; if the
+        decorated is applied on a class and if this argument is not specified,
+        the manager will provide IViewletManager interface.
     """
 
     venusian = venusian  # for testing injection
@@ -249,22 +281,22 @@
     def __call__(self, wrapped):
         settings = self.__dict__.copy()
 
-        def callback(context, name, ob):
+        def callback(context, name, obj):  # pylint: disable=unused-argument
             cdict = {'__name__': settings.get('name')}
             if 'permission' in settings:
                 cdict['permission'] = settings.get('permission')
 
-            if issubclass(ob, Interface):
+            if issubclass(obj, Interface):
                 class_ = settings.get('class_', ViewletManager)
-                provides = ob
+                provides = obj
             else:
-                class_ = ob
+                class_ = obj
                 provides = settings.get('provides', IViewletManager)
             new_class = ViewletManagerFactory(settings.get('name'), provides, (class_,), cdict)
 
-            logger.debug("Registering viewlet manager {0} ({1})".format(settings.get('name'),
+            LOGGER.debug("Registering viewlet manager {0} ({1})".format(settings.get('name'),
                                                                         str(new_class)))
-            config = context.config.with_package(info.module)
+            config = context.config.with_package(info.module)  # pylint: disable=no-member
             config.registry.registerAdapter(new_class,
                                             (settings.get('context', Interface),
                                              settings.get('layer', IRequest),
@@ -273,12 +305,12 @@
 
         info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
 
-        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
--- a/src/pyams_viewlet/metaconfigure.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/metaconfigure.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,22 +10,23 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+"""PyAMS_viewlet.metaconfigure module
 
-# import interfaces
-from pyams_viewlet.interfaces import IViewletManager, IViewlet
-from pyramid.interfaces import IRequest, IView
+This module provides ZCML directives handlers.
+"""
 
-# import packages
-from pyams_viewlet.manager import ViewletManager, ViewletManagerFactory
 from pyramid.exceptions import ConfigurationError
+from pyramid.interfaces import IRequest, IView
 from zope.component import zcml
 from zope.component.interface import provideInterface
 from zope.interface import Interface, classImplements
 
+from pyams_viewlet.interfaces import IViewlet, IViewletManager
+from pyams_viewlet.manager import ViewletManager, ViewletManagerFactory
+
+
+__docformat__ = 'restructuredtext'
+
 
 def ViewletManagerDirective(_context, name,
                             context=Interface,
@@ -34,6 +35,8 @@
                             provides=IViewletManager,
                             class_=None,
                             permission=None):
+    # pylint: disable=invalid-name,too-many-arguments
+    """Viewlet manager ZCML directive"""
 
     # If class is not given we use the basic viewlet manager.
     if class_ is None:
@@ -66,16 +69,19 @@
                      attribute='render',
                      permission=None,
                      **kwargs):
+    # pylint: disable=invalid-name,too-many-arguments
+    """Viewlet ZCML directive"""
 
     # Make sure the has the right form, if specified.
     if attribute != 'render':
         if not hasattr(class_, attribute):
             raise ConfigurationError("The provided class doesn't have the specified attribute")
 
-    cdict = {}
-    cdict['__name__'] = name
-    cdict['__page_attribute__'] = attribute
-    cdict['permission'] = permission
+    cdict = {
+        '__name__': name,
+        '__page_attribute__': attribute,
+        'permission': permission
+    }
     cdict.update(kwargs)
     new_class = type(class_.__name__, (class_,), cdict)
 
--- a/src/pyams_viewlet/metadirectives.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/metadirectives.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,19 +10,20 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+"""PyAMS_viewlet.metadirectives module
 
-# import interfaces
-from pyams_viewlet.interfaces import IViewletManager
+This template provides definition of ZCML directives.
+"""
 
-# import packages
 from zope.configuration.fields import GlobalInterface, GlobalObject
 from zope.interface import Interface
 from zope.schema import TextLine
 
+from pyams_viewlet.interfaces import IViewletManager
+
+
+__docformat__ = 'restructuredtext'
+
 
 class IContentProvider(Interface):
     """A directive to register a simple content provider.
@@ -59,9 +60,12 @@
                           required=False)
 
     layer = 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'.""",
+                            description="A skin is composed of layers; layers are defined as "
+                                        "interfaces, which are provided to the request when the "
+                                        "skin is applied. 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 "
+                                        "IRequest, which is the base interface of any request.",
                             required=False)
 
 
@@ -101,4 +105,5 @@
 
 
 # Arbitrary keys and values are allowed to be passed to the viewlet.
+# pylint: disable=no-value-for-parameter
 IViewletDirective.setTaggedValue('keyword_arguments', True)
--- a/src/pyams_viewlet/provider.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/provider.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,24 +10,32 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_viewlet.provider module
+
+This module provides the "provider:" TALES expression, which allows inclusion of any registered
+content provider into a Chameleon or ZPT template.
+"""
 
 import re
 
 from chameleon.astutil import Symbol
 from chameleon.tales import StringExpr
 from pyramid_zope_request import PyramidPublisherRequest
-from zope.contentprovider.interfaces import BeforeUpdateEvent, ContentProviderLookupError, IContentProvider
+from zope.contentprovider.interfaces import BeforeUpdateEvent, ContentProviderLookupError, \
+    IContentProvider
 from zope.contentprovider.tales import addTALNamespaceData
 from zope.location.interfaces import ILocation
 
 from pyams_utils.tales import ContextExprMixin
 
 
-FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
-ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
+__docformat__ = 'restructuredtext'
+
 
-CONTENT_PROVIDER_NAME = re.compile('([A-Za-z0-9_\-\.]+)')
+FUNCTION_EXPRESSION = re.compile(r'(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
+ARGUMENTS_EXPRESSION = re.compile(r'[^(,)]+')
+
+CONTENT_PROVIDER_NAME = re.compile(r'([A-Za-z0-9_\-\.]+)')
 
 
 def render_content_provider(econtext, name):
@@ -67,7 +75,7 @@
         except ValueError:
             args = arg.split('.')
             result = econtext.get(args.pop(0))
-            for arg in args:
+            for arg in args:  # pylint: disable=redefined-argument-from-local
                 result = getattr(result, arg)
             return result
         else:
@@ -77,7 +85,7 @@
     context = econtext.get('context')
     request = econtext.get('request')
     if isinstance(request, PyramidPublisherRequest):
-        request = request._request
+        request = request._request  # pylint: disable=protected-access
     view = econtext.get('view')
 
     args, kwargs = [], {}
--- a/src/pyams_viewlet/tests/__init__.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/tests/__init__.py	Wed Nov 27 15:57:21 2019 +0100
@@ -1,1 +1,29 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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_viewlet 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
--- a/src/pyams_viewlet/tests/test_utilsdocs.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/tests/test_utilsdocs.py	Wed Nov 27 15:57:21 2019 +0100
@@ -1,5 +1,3 @@
-### -*- coding: utf-8 -*- ####################################################
-##############################################################################
 #
 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
 # All Rights Reserved.
@@ -11,23 +9,25 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-##############################################################################
 
 """
 Generic Test case for pyams_viewlet doctest
 """
+
+import doctest
+import os
+import unittest
+
+from pyams_viewlet.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()
@@ -35,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,
@@ -53,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')
-
--- a/src/pyams_viewlet/tests/test_utilsdocstrings.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/tests/test_utilsdocstrings.py	Wed Nov 27 15:57:21 2019 +0100
@@ -1,5 +1,3 @@
-### -*- coding: utf-8 -*- ####################################################
-##############################################################################
 #
 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
 # All Rights Reserved.
@@ -11,21 +9,22 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-##############################################################################
 
 """
 Generic Test case for pyams_viewlet 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 = []
@@ -45,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:
@@ -57,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')
--- a/src/pyams_viewlet/viewlet.py	Fri Jan 18 15:32:56 2019 +0100
+++ b/src/pyams_viewlet/viewlet.py	Wed Nov 27 15:57:21 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_viewlet.viewlet module
+
+This module provides base content providers and viewlets classes, as well as a decorators
+which can be used instead of ZCML declarations to register content providers and viewlets.
+"""
 
 import logging
 
@@ -23,12 +27,14 @@
 from pyams_template.template import get_view_template
 from pyams_viewlet.interfaces import IViewlet, IViewletManager
 
+__docformat__ = 'restructuredtext'
 
-logger = logging.getLogger('PyAMS (viewlet)')
+
+LOGGER = logging.getLogger('PyAMS (viewlet)')
 
 
 @implementer(IContentProvider)
-class EmptyContentProvider(object):
+class EmptyContentProvider:
     """Empty content provider base class"""
 
     permission = None
@@ -38,15 +44,17 @@
         self.request = request
 
     def __call__(self):
-        if self.permission and not self.request.has_permission(self.permission, context=self.context):
+        if self.permission and not self.request.has_permission(self.permission,
+                                                               context=self.context):
             return ''
         self.update()
         return self.render()
 
     def update(self):
-        pass
+        """See `IContentProvider` interface"""
 
-    def render(self):
+    def render(self):  # pylint: disable=no-self-use
+        """See `IContentProvider` interface"""
         return ''
 
 
@@ -71,7 +79,7 @@
         self.view = self.__parent__ = view
 
 
-class contentprovider_config(object):
+class contentprovider_config:  # pylint: disable=invalid-name
     """Class decorator used to declare a content provider
 
     You can provide same arguments as in 'viewlet' ZCML directive:
@@ -95,22 +103,22 @@
     def __call__(self, wrapped):
         settings = self.__dict__.copy()
 
-        def callback(context, name, ob):
+        def callback(context, name, obj):  # pylint: disable=unused-argument
             cdict = {
                 '__name__': settings.get('name'),
-                '__module__': ob.__module__
+                '__module__': obj.__module__
             }
             if 'permission' in settings:
                 settings['permission'] = settings.get('permission')
 
-            bases = (ob,)
-            if not IContentProvider.implementedBy(ob):
+            bases = (obj,)
+            if not IContentProvider.implementedBy(obj):  # pylint: disable=no-value-for-parameter
                 bases = bases + (ViewContentProvider,)
             new_class = type('<ViewContentProvider %s>' % settings.get('name'), bases, cdict)
 
-            logger.debug("Registering content provider {0} ({1})".format(settings.get('name'),
+            LOGGER.debug("Registering content provider {0} ({1})".format(settings.get('name'),
                                                                          str(new_class)))
-            config = context.config.with_package(info.module)
+            config = context.config.with_package(info.module)  # pylint: disable=no-member
             config.registry.registerAdapter(new_class,
                                             (settings.get('context', Interface),
                                              settings.get('layer', IRequest),
@@ -119,19 +127,19 @@
 
         info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
 
-        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
 
 
 @implementer(IViewlet)
-class EmptyViewlet(object):
+class EmptyViewlet:
     """Empty viewlet base class"""
 
     permission = None
@@ -143,9 +151,10 @@
         self.manager = manager
 
     def update(self):
-        pass
+        """See `IContentProvider` interface"""
 
-    def render(self):
+    def render(self):  # pylint: disable=no-self-use
+        """See `IContentProvider` interface"""
         return ''
 
 
@@ -155,7 +164,7 @@
     render = get_view_template()
 
 
-class viewlet_config(object):
+class viewlet_config:  # pylint: disable=invalid-name
     """Class decorator used to declare a viewlet
 
     You can provide same arguments as in 'viewlet' ZCML directive:
@@ -181,24 +190,24 @@
     def __call__(self, wrapped):
         settings = self.__dict__.copy()
 
-        def callback(context, name, ob):
+        def callback(context, name, obj):  # pylint: disable=unused-argument
             cdict = {
                 '__name__': settings.get('name'),
-                '__module__': ob.__module__
+                '__module__': obj.__module__
             }
             if 'permission' in settings:
                 cdict['permission'] = settings.get('permission')
             if 'weight' in settings:
                 cdict['weight'] = settings.get('weight')
 
-            bases = (ob,)
-            if not IViewlet.implementedBy(ob):
+            bases = (obj,)
+            if not IViewlet.implementedBy(obj):  # pylint: disable=no-value-for-parameter
                 bases = bases + (Viewlet,)
             new_class = type('<Viewlet %s>' % settings.get('name'), bases, cdict)
 
-            logger.debug("Registering viewlet {0} ({1})".format(settings.get('name'),
+            LOGGER.debug("Registering viewlet {0} ({1})".format(settings.get('name'),
                                                                 str(new_class)))
-            config = context.config.with_package(info.module)
+            config = context.config.with_package(info.module)  # pylint: disable=no-member
             config.registry.registerAdapter(new_class,
                                             (settings.get('context', Interface),
                                              settings.get('layer', IRequest),
@@ -208,12 +217,12 @@
 
         info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
 
-        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