Added modules documentation and removed Pylint warnings and errors dev-tf
authorThierry Florac <tflorac@ulthar.net>
Sat, 23 Nov 2019 14:51:46 +0100
branchdev-tf
changeset 419 05ff71a02b2d
parent 418 55fc762a7afa
child 420 b881e57cf6a5
Added modules documentation and removed Pylint warnings and errors
src/pyams_utils/adapter.py
src/pyams_utils/context.py
src/pyams_utils/decorator.py
src/pyams_utils/include.py
src/pyams_utils/interfaces/__init__.py
src/pyams_utils/interfaces/data.py
src/pyams_utils/interfaces/inherit.py
src/pyams_utils/interfaces/intids.py
src/pyams_utils/interfaces/site.py
src/pyams_utils/interfaces/size.py
src/pyams_utils/interfaces/tales.py
src/pyams_utils/interfaces/text.py
src/pyams_utils/interfaces/timezone.py
src/pyams_utils/interfaces/traversing.py
src/pyams_utils/interfaces/tree.py
src/pyams_utils/interfaces/url.py
src/pyams_utils/interfaces/zeo.py
src/pyams_utils/protocol/http.py
src/pyams_utils/protocol/tcp.py
src/pyams_utils/protocol/xmlrpc.py
src/pyams_utils/registry.py
src/pyams_utils/scripts/zodb.py
src/pyams_utils/site.py
src/pyams_utils/tales.py
src/pyams_utils/tests/__init__.py
src/pyams_utils/tests/test_utilsdocs.py
src/pyams_utils/tests/test_utilsdocstrings.py
src/pyams_utils/text.py
src/pyams_utils/timezone/__init__.py
src/pyams_utils/timezone/utility.py
src/pyams_utils/timezone/vocabulary.py
src/pyams_utils/traversing.py
src/pyams_utils/url.py
src/pyams_utils/widget/decimal.py
src/pyams_utils/wsgi.py
src/pyams_utils/zodb.py
--- a/src/pyams_utils/adapter.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/adapter.py	Sat Nov 23 14:51:46 2019 +0100
@@ -27,7 +27,7 @@
 from zope.location import locate as zope_locate
 
 from pyams_utils.factory import get_object_factory, is_interface
-from pyams_utils.registry import get_current_registry, get_global_registry
+from pyams_utils.registry import get_current_registry
 
 
 __docformat__ = 'restructuredtext'
--- a/src/pyams_utils/context.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/context.py	Sat Nov 23 14:51:46 2019 +0100
@@ -50,7 +50,7 @@
         sys.stderr = err
 
 
-class ContextSelector(object):  # pylint: disable=too-few-public-methods
+class ContextSelector:  # pylint: disable=too-few-public-methods
     """Interface based context selector
 
     This selector can be used as a predicate to define a class or an interface that the context
--- a/src/pyams_utils/decorator.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/decorator.py	Sat Nov 23 14:51:46 2019 +0100
@@ -63,6 +63,5 @@
     if len(msg) == 1 and callable(msg[0]):
         message = u''
         return decorator(msg[0])
-    else:
-        message = msg[0]
-        return decorator
+    message = msg[0]
+    return decorator
--- a/src/pyams_utils/include.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/include.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,6 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.include module
+
+This module is used for Pyramid integration
+"""
+
 from chameleon import PageTemplateFile
 from persistent.interfaces import IPersistent
 from z3c.pt.pagetemplate import PageTemplateFile as Z3cPageTemplateFile
@@ -60,7 +65,7 @@
     config.registry.registerAdapter(KeyReferenceToPersistent, (IPersistent, ), IKeyReference)
 
     try:
-        import pyams_zmi
+        import pyams_zmi  # pylint: disable=import-outside-toplevel,unused-import
     except ImportError:
         config.scan(ignore='pyams_utils.zmi')
     else:
--- a/src/pyams_utils/interfaces/__init__.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/__init__.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,18 +10,20 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-"""PyAMS_utils interfaces module
+"""PyAMS_utils.interfaces package
 
 This module defines several generic constants and interfaces.
+
 It is also used to provide translations to common zope.schema exceptions.
 """
 
-__docformat__ = 'restructuredtext'
+from zope.interface import Interface
+from zope.schema.interfaces import ConstraintNotSatisfied, InvalidDottedName, InvalidId, \
+    InvalidURI, InvalidValue, NotAContainer, NotAnIterator, NotUnique, RequiredMissing, \
+    SchemaNotFullyImplemented, SchemaNotProvided, TooBig, TooLong, TooShort, TooSmall, Unbound, \
+    WrongContainedType, WrongType
 
-from zope.interface import Interface
-from zope.schema.interfaces import ConstraintNotSatisfied, InvalidDottedName, InvalidId, InvalidURI, InvalidValue, \
-    NotAContainer, NotAnIterator, NotUnique, RequiredMissing, SchemaNotFullyImplemented, SchemaNotProvided, TooBig, \
-    TooLong, TooShort, TooSmall, Unbound, WrongContainedType, WrongType
+__docformat__ = 'restructuredtext'
 
 from pyams_utils import _
 
@@ -50,7 +52,6 @@
 NotAContainer.__doc__ = _("""Not a container""")
 NotAnIterator.__doc__ = _("""Not an iterator""")
 
-
 #
 # Custom permissions
 #
@@ -65,7 +66,8 @@
 '''View permission is a custom permission used to view contents'''
 
 MANAGE_PERMISSION = 'manage'
-'''Permission used to manage basic information; this permission is generally not used by custom contents'''
+'''Permission used to manage basic information; this permission is generally not used by custom
+contents'''
 
 VIEW_SYSTEM_PERMISSION = 'pyams.ViewSystem'
 '''Permission used to access management screens'''
@@ -106,14 +108,15 @@
 
     This interface can be used to register an "interface's object factory".
     For a given interface, such factory can be used to get an instance of an object providing
-    this interface; several factories can be registered for the same interface if they have distinct
-    names. See :py:mod:`pyams_utils.factory` module.
+    this interface; several factories can be registered for the same interface if they have
+    distinct names. See :py:mod:`pyams_utils.factory` module.
     """
 
 
 class ICacheKeyValue(Interface):
     """Interface used to get string representation of a given object as cache key
 
-    Several default adapters are given for objects (using their "id()"), strings (using string as key)
-    and for persistent objects (using their persistent OID); you are free to provide your own adapters.
+    Several default adapters are given for objects (using their "id()"), strings (using string as
+    key) and for persistent objects (using their persistent OID); you are free to provide your
+    own adapters.
     """
--- a/src/pyams_utils/interfaces/data.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/data.py	Sat Nov 23 14:51:46 2019 +0100
@@ -17,11 +17,11 @@
 HTML code.
 """
 
-__docformat__ = 'restructuredtext'
-
 from zope.interface import Interface
 from zope.schema import Dict
 
+__docformat__ = 'restructuredtext'
+
 
 class IObjectData(Interface):
     """Object data generic interface
--- a/src/pyams_utils/interfaces/inherit.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/inherit.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,17 +10,19 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.inherit module
 
-# import standard library
+This module defines interfaces which are used by the :py:mod:`inherit <pyams_utils.inherit>`
+module
+"""
 
-# import interfaces
 from pyramid.interfaces import ILocation
 
-# import packages
 from zope.interface import Attribute
 from zope.schema import Bool
 
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
--- a/src/pyams_utils/interfaces/intids.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/intids.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,15 +10,16 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.intids module
 
-# import standard packages
+Small set of interfaces used by IIntIds utilities.
+"""
 
-# import interfaces
 from zope.interface import Interface
+from zope.schema import Int, TextLine
 
-# import packages
-from zope.schema import TextLine, Int
+
+__docformat__ = 'restructuredtext'
 
 
 #
@@ -35,5 +36,6 @@
     """Interface used to get unique ID of an object"""
 
     oid = TextLine(title="Unique ID",
-                   description="Globally unique identifier of this object can be used to create internal links",
+                   description="Globally unique identifier of this object can be used to create "
+                               "internal links",
                    readonly=True)
--- a/src/pyams_utils/interfaces/site.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/site.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,6 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.interfaces.site module
+
+This module provides interfaces related to site management, generations and utilities
+"""
+
 from zope.annotation.interfaces import IAttributeAnnotatable
 from zope.interface import Attribute, Interface
 from zope.interface.interfaces import IObjectEvent
--- a/src/pyams_utils/interfaces/size.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/size.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,17 +10,16 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_utils.interfaces.size module
 
-# import standard library
+This module provides a simple interface for objects handling length attribute
+"""
 
-# import interfaces
-
-# import packages
 from zope.interface import Interface
 from zope.schema import Int
 
+__docformat__ = 'restructuredtext'
+
 
 class ILength(Interface):
     """Length interface"""
--- a/src/pyams_utils/interfaces/tales.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/tales.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,14 +10,15 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.tales module
 
-# import standard library
+TALES extensions are custom adapters which can be used to extend Chameleon and Zope templates.
+"""
 
-# import interfaces
+from zope.interface import Interface
 
-# import packages
-from zope.interface import Interface
+
+__docformat__ = 'restructuredtext'
 
 
 class ITALESExtension(Interface):
--- a/src/pyams_utils/interfaces/text.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/text.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,15 +10,16 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.text module
+
+This module provides a single interface used by HTML rendering adapters, which are used to convert
+any object to an HTML representation.
+"""
+
+from zope.interface import Attribute, Interface
 
 
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Attribute, Interface
+__docformat__ = 'restructuredtext'
 
 from pyams_utils import _
 
--- a/src/pyams_utils/interfaces/timezone.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/timezone.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,17 +10,17 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.timezone module
+
+This module provides timezone utility interface and schema field
+"""
+
+from zope.interface import Interface, implementer
+from zope.schema import Choice
+from zope.schema.interfaces import IChoice
 
 
-# import standard library
-
-# import interfaces
-from zope.schema.interfaces import IChoice
-
-# import packages
-from zope.interface import implementer, Interface
-from zope.schema import Choice
+__docformat__ = 'restructuredtext'
 
 from pyams_utils import _
 
--- a/src/pyams_utils/interfaces/traversing.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/traversing.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,16 +10,18 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.traversing module
+
+The IPathElements is used by an Hypatia index to store internal IDs of parents of a given
+objects; this allows to query catalog for objects which are located "inside" a given parent
+object, identified by it's internal ID.
+"""
+
+from zope.interface import Interface
+from zope.schema import Int, List
 
 
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Interface
-from zope.schema import List, Int
+__docformat__ = 'restructuredtext'
 
 
 class IPathElements(Interface):
--- a/src/pyams_utils/interfaces/tree.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/tree.py	Sat Nov 23 14:51:46 2019 +0100
@@ -1,7 +1,5 @@
-### -*- coding: utf-8 -*- ####################################################
-##############################################################################
 #
-# Copyright (c) 2012 Thierry Florac <tflorac AT ulthar.net>
+# 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,
@@ -11,7 +9,11 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-##############################################################################
+
+"""PyAMS_utils.interfaces.tree module
+
+The interfaces provided by this module are used to manage trees.
+"""
 
 from zope.interface import Interface, Attribute
 
--- a/src/pyams_utils/interfaces/url.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/url.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,11 +10,20 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.url module
+
+These interfaces are used to define different types of URLs which can be used in a web site.
+These includes absolute URLs, canonical URLs and related URLs.
+
+See :py:mod:`PyAMS URL module <pyams_utils.url>` for a longer description.
+"""
 
 from zope.interface import Interface
 
 
+__docformat__ = 'restructuredtext'
+
+
 class ICanonicalURL(Interface):
     """Interface used to get content's canonical URL"""
 
--- a/src/pyams_utils/interfaces/zeo.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/interfaces/zeo.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,16 +10,16 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.zeo module
+
+This module provides interface definition for a ZEO connection
+"""
+
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Int, Password, TextLine
 
 
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Interface, Attribute
-from zope.schema import TextLine, Int, Password, Bool
+__docformat__ = 'restructuredtext'
 
 from pyams_utils import _
 
@@ -51,11 +51,13 @@
                         required=False)
 
     password = Password(title=_("ZEO password"),
-                        description=_("User password on ZEO server; only for ZEO server before 5.0"),
+                        description=_(
+                            "User password on ZEO server; only for ZEO server before 5.0"),
                         required=False)
 
     server_realm = TextLine(title=_("ZEO server realm"),
-                            description=_("Realm name on ZEO server; only for ZEO server before 5.0"),
+                            description=_(
+                                "Realm name on ZEO server; only for ZEO server before 5.0"),
                             required=False)
 
     blob_dir = TextLine(title=_("BLOBs directory"),
@@ -63,8 +65,9 @@
                         required=False)
 
     shared_blob_dir = Bool(title=_("Shared BLOBs directory ?"),
-                           description=_("""Flag whether the blob_dir is a server-shared filesystem """
-                                         """that should be used instead of transferring blob data over zrpc."""),
+                           description=_(
+                               "Flag whether the blob_dir is a server-shared filesystem "
+                               "that should be used instead of transferring blob data over zrpc."),
                            required=True,
                            default=False)
 
--- a/src/pyams_utils/protocol/http.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/protocol/http.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,49 +10,54 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.protocol.http module
+
+This module provides an HTTP client class, which allows to easilly define proxies and
+authentication headers.
+"""
+
+import urllib.parse
+
+import httplib2
+
+
 __docformat__ = 'restructuredtext'
 
 
-# import standard library
-import httplib2
-import urllib.parse
-
-# import interfaces
-
-# import packages
-
-
-class HTTPClient(object):
+class HTTPClient:
+    # pylint: disable=too-many-instance-attributes
     """HTTP client with proxy support"""
 
-    def __init__(self, method, protocol, servername, url, params={}, credentials=(),
-                 proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+    def __init__(self, method, protocol, servername, url, params=None, credentials=(),
+                 proxy=(), rdns=True, proxy_auth=(), timeout=None, headers=None):
+        # pylint: disable=too-many-arguments
         """Intialize HTTP connection"""
         self.connection = None
         self.method = method
         self.protocol = protocol
         self.servername = servername
         self.url = url
-        self.params = params
+        self.params = params or {}
         self.location = None
         self.credentials = credentials
         self.proxy = proxy
         self.rdns = rdns
         self.proxy_auth = proxy_auth
         self.timeout = timeout
-        self.headers = headers
-        if 'User-Agent' not in headers:
+        self.headers = headers or {}
+        if 'User-Agent' not in self.headers:
             self.headers['User-Agent'] = 'PyAMS HTTP Client/1.0'
 
     def get_response(self):
         """Common HTTP request"""
         if self.proxy and (len(self.proxy) == 2):
-            proxy_info = httplib2.ProxyInfo(httplib2.socks.PROXY_TYPE_HTTP,
-                                            proxy_host=self.proxy[0],
-                                            proxy_port=self.proxy[1],
-                                            proxy_rdns=self.rdns,
-                                            proxy_user=self.proxy_auth and self.proxy_auth[0] or None,
-                                            proxy_pass=self.proxy_auth and self.proxy_auth[1] or None)
+            proxy_info = httplib2.ProxyInfo(
+                httplib2.socks.PROXY_TYPE_HTTP,
+                proxy_host=self.proxy[0],
+                proxy_port=self.proxy[1],
+                proxy_rdns=self.rdns,
+                proxy_user=self.proxy_auth and self.proxy_auth[0] or None,
+                proxy_pass=self.proxy_auth and self.proxy_auth[1] or None)
         else:
             proxy_info = None
         http = httplib2.Http(timeout=self.timeout, proxy_info=proxy_info)
@@ -65,14 +70,17 @@
         return response, content
 
 
-def get_client(method, protocol, servername, url, params={}, credentials=(), proxy=(),
-               rdns=True, proxy_auth=(), timeout=None, headers={}):
+def get_client(method, protocol, servername, url, params=None, credentials=(), proxy=(),
+               rdns=True, proxy_auth=(), timeout=None, headers=None):
+    # pylint: disable=too-many-arguments
     """HTTP client factory"""
     return HTTPClient(method, protocol, servername, url, params, credentials, proxy,
                       rdns, proxy_auth, timeout, headers)
 
 
-def get_client_from_url(url, credentials=(), proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+def get_client_from_url(url, credentials=(), proxy=(), rdns=True, proxy_auth=(), timeout=None,
+                        headers=None):
+    # pylint: disable=too-many-arguments
     """HTTP client factory from URL"""
     elements = urllib.parse.urlparse(url)
     return HTTPClient('GET', elements.scheme, elements.netloc, elements.path, elements.params,
--- a/src/pyams_utils/protocol/tcp.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/protocol/tcp.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,12 +10,17 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.protocol.tcp module
+
+This module only provides a single function, used to know if a given TCP port is already in use
+"""
 
 import socket
 
+__docformat__ = 'restructuredtext'
+
 
 def is_port_in_use(port, hostname='localhost'):
     """Check if given port is already used locally"""
-    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
-        return s.connect_ex((hostname, port)) == 0
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        return sock.connect_ex((hostname, port)) == 0
--- a/src/pyams_utils/protocol/xmlrpc.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/protocol/xmlrpc.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,6 +10,13 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.protocol.xmlrpc module
+
+This module provides a few set of classes and functions usable to improve XML-RPC client usage.
+
+It provides custom transports and allows storage of response cookies
+"""
+
 import base64
 import http.client
 import http.cookiejar
@@ -17,7 +24,6 @@
 import urllib.request
 import xmlrpc.client
 
-
 try:
     import gzip
 except ImportError:
@@ -27,6 +33,7 @@
 
 
 class XMLRPCCookieAuthTransport(xmlrpc.client.Transport):
+    # pylint: disable=too-many-instance-attributes
     """An XML-RPC transport handling authentication via cookies"""
 
     _http_connection = http.client.HTTPConnection
@@ -34,6 +41,7 @@
 
     def __init__(self, user_agent, credentials=(), cookies=None,
                  timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+        # pylint: disable=protected-access,too-many-arguments
         xmlrpc.client.Transport.__init__(self)
         self.user_agent = user_agent
         self.credentials = credentials
@@ -75,8 +83,8 @@
         self.send_content(connection, request_body)
         return connection
 
-    # override the send_host hook to also send authentication info
     def send_auth(self, connection):
+        """Override the send_host hook to also send authentication info"""
         if (self.cookies is not None) and (len(self.cookies) > 0):
             for cookie in self.cookies:
                 connection.putheader('Cookie', '%s=%s' % (cookie.name, cookie.value))
@@ -85,41 +93,46 @@
             auth = 'Basic %s' % creds
             connection.putheader('Authorization', auth)
 
-    # send content type
-    def send_content_type(self, connection):
+    @staticmethod
+    def send_content_type(connection):
+        """Send content type"""
         connection.putheader('Content-Type', 'text/xml')
 
-    # send user agent
     def send_user_agent(self, connection):
+        """Send user agent"""
         connection.putheader('User-Agent', self.user_agent)
 
-    # send custom headers
     def send_headers(self, connection, headers):
+        """Send custom headers"""
         xmlrpc.client.Transport.send_headers(self, connection, headers)
-        for k, v in (self.headers or {}).items():
-            connection.putheader(k, v)
+        for key, value in (self.headers or {}).items():
+            connection.putheader(key, value)
 
-    # dummy request class for extracting cookies
     class CookieRequest(urllib.request.Request):
-        pass
+        """Dummy request class used for extracting cookies"""
 
-    # dummy response info headers helper
     class CookieResponseHelper:
+        """Dummy response headers helper"""
+
         def __init__(self, response):
             self.response = response
 
         def getheaders(self, header):
+            """Get response headers"""
             return self.response.msg.getallmatchingheaders(header)
 
-    # dummy response class for extracting cookies
     class CookieResponse:
+        """Dummy response class used to extract cookies"""
+
         def __init__(self, response):
             self.response = response
 
         def info(self):
+            """Get response info from cookies"""
             return XMLRPCCookieAuthTransport.CookieResponseHelper(self.response)
 
     def get_response(self, connection, host, handler):
+        """Get server response"""
         response = connection.getresponse()
         # extract cookies from response headers
         if self.cookies is not None:
@@ -143,6 +156,7 @@
 
 def get_client(uri, credentials=(), verbose=False, allow_none=0,
                timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+    # pylint: disable=protected-access,too-many-arguments
     """Get an XML-RPC client which supports basic authentication"""
     if uri.startswith('https:'):
         transport = SecureXMLRPCCookieAuthTransport(
@@ -156,6 +170,7 @@
 
 def get_client_with_cookies(uri, credentials=(), verbose=False, allow_none=0,
                             timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None, cookies=None):
+    # pylint: disable=protected-access,too-many-arguments
     """Get an XML-RPC client which supports authentication through cookies"""
     if cookies is None:
         cookies = http.cookiejar.CookieJar()
--- a/src/pyams_utils/registry.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/registry.py	Sat Nov 23 14:51:46 2019 +0100
@@ -21,7 +21,6 @@
 See :ref:`zca` to get a brief introduction about using a local registry with PyAMS packages.
 """
 
-
 import logging
 import threading
 
@@ -52,12 +51,14 @@
     _registry = None
 
     def get_registry(self):
+        """Return local registry"""
         return self._registry
 
     def set_registry(self, registry):
+        """Define local registry"""
         self._registry = registry
 
-local_registry = LocalRegistry()
+local_registry = LocalRegistry()  # pylint: disable=invalid-name
 
 
 def get_local_registry():
@@ -74,7 +75,7 @@
 
 
 @subscriber(INewRequest)
-def handle_new_request(event):
+def handle_new_request(event):  # pylint: disable=unused-argument
     """New request event subscriber
 
     Is used to initialize local registry to None for any new request
@@ -183,7 +184,7 @@
             yield utility
 
 
-def get_all_utilities_registered_for(interface):
+def get_all_utilities_registered_for(interface):  # pylint: disable=invalid-name
     """Get list of registered utilities for given interface
 
     Do a registry lookup for matching utilities into local registry first, then on each registry
@@ -196,17 +197,17 @@
     return result
 
 
-class utility_config(object):  # pylint: disable=invalid-name
+class utility_config:  # pylint: disable=invalid-name
     """Function or class decorator to register a utility in the global registry
 
     :param str name: default=''; name under which the utility is registered
     :param Interface provides: the interface for which the utility is registered
 
-    Please note that a single utility can be registered several times (using several annotations), with
-    different names.
+    Please note that a single utility can be registered several times (using several annotations),
+    with different names.
 
-    If several utilities are registered for the same interface with the same name, the last registered
-    utility will override the previous ones.
+    If several utilities are registered for the same interface with the same name, the last
+    registered utility will override the previous ones.
     """
 
     venusian = venusian
@@ -218,13 +219,13 @@
         settings = self.__dict__.copy()
         depth = settings.pop('_depth', 0)
 
-        def callback(context, name, ob):
-            if type(ob) is type:
-                factory = ob
+        def callback(context, name, obj):
+            if isinstance(obj, type):
+                factory = obj
                 component = None
             else:
                 factory = None
-                component = ob
+                component = obj
 
             provides = settings.get('provides')
             if provides is None:
--- a/src/pyams_utils/scripts/zodb.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/scripts/zodb.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,19 +10,22 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.scripts.zodb module
 
+This module provides a single function which will be used by "pyams_upgrade" command line
+script to upgrade existing database schema when installing a new application release
+"""
 
-# import standard library
 import argparse
 import sys
 import textwrap
 
-# import interfaces
+from pyramid.paster import bootstrap
 
-# import packages
 from pyams_utils.site import site_upgrade
-from pyramid.paster import bootstrap
+
+
+__docformat__ = 'restructuredtext'
 
 
 def upgrade_site():
@@ -38,7 +41,7 @@
 
     config_uri = args.config_uri
     env = bootstrap(config_uri)
-    settings, closer = env['registry'].settings, env['closer']
+    closer = env['closer']
     try:
         site_upgrade(env['request'])
     finally:
--- a/src/pyams_utils/site.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/site.py	Sat Nov 23 14:51:46 2019 +0100
@@ -11,6 +11,12 @@
 #
 
 """PyAMS_utils.site module
+
+This modules provides classes of elements which can be used as application or site "root"
+objects.
+
+Il also provides functions which are used to manage site's "generations", used to upgrade
+objects while migrating from one version to another.
 """
 
 from persistent.dict import PersistentDict
@@ -48,7 +54,7 @@
 
     BaseSiteRoot defines a basic ACL which gives all permissions to system administrator,
     and 'public' permission to everyone. But this ACL is generally overriden in subclasses
-    which also inherit from :class:`pyams_security.security.ProtectedObject`.
+    which also inherit from :py:class:`ProtectedObject <pyams_security.security.ProtectedObject>`.
     """
 
     __acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS),
@@ -64,7 +70,9 @@
     Gives access to local site manager from */++etc++site* URL
     """
 
-    def traverse(self, name, furtherpath=None):
+    def traverse(self, name, furtherpath=None):  # pylint: disable=unused-argument
+        """Traverse to site manager;
+        see :py:class:`ITraversable <zope.traversing.interfaces.ITraversable>`"""
         if name == 'site':
             return self.context.getSiteManager()
         raise NotFound
@@ -96,8 +104,8 @@
             factory = request.registry.queryUtility(ISiteRootFactory, default=BaseSiteRoot)
         application = root[application_key] = factory()
         if IPossibleSite.providedBy(application):
-            sm = LocalSiteManager(application, default_folder=False)
-            application.setSiteManager(sm)
+            lsm = LocalSiteManager(application, default_folder=False)
+            application.setSiteManager(lsm)
         try:
             # if some components require a valid and complete registry
             # with all registered utilities, they can subscribe to
@@ -106,7 +114,7 @@
             get_current_registry().notify(NewLocalSiteCreatedEvent(application))
         finally:
             hooks.setSite(None)
-        import transaction
+        import transaction  # pylint: disable=import-outside-toplevel
         transaction.commit()
     return application
 
@@ -145,7 +153,7 @@
                 generations[name] = utility.generation
         finally:
             hooks.setSite(None)
-        import transaction
+        import transaction  # pylint: disable=import-outside-toplevel
         transaction.commit()
     return application
 
@@ -168,10 +176,10 @@
     for interface, name, factory, default_id in utilities:
         utility = query_utility(interface, name=name)
         if utility is None:
-            sm = site.getSiteManager()
-            if default_id in sm:
+            lsm = site.getSiteManager()
+            if default_id in lsm:
                 continue
             utility = factory()
             registry.notify(ObjectCreatedEvent(utility))
-            sm[default_id] = utility
-            sm.registerUtility(utility, interface, name=name)
+            lsm[default_id] = utility
+            lsm.registerUtility(utility, interface, name=name)
--- a/src/pyams_utils/tales.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/tales.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.tales module
+
+This module provides a custom TALES extension engine, which allows you to define custom
+TALES expressions which can be used from Chameleon or Zope templates.
+"""
 
 import re
 
@@ -21,8 +25,10 @@
 
 from pyams_utils.interfaces.tales import ITALESExtension
 
+__docformat__ = 'restructuredtext'
 
-class ContextExprMixin(object):
+
+class ContextExprMixin:
     """Mixin-class for expression compilers"""
 
     transform = None
@@ -36,8 +42,8 @@
         return assignment + transform
 
 
-FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
-ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
+FUNCTION_EXPRESSION = re.compile(r'(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
+ARGUMENTS_EXPRESSION = re.compile(r'[^(,)]+')
 
 
 def render_extension(econtext, name):
@@ -72,7 +78,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:
--- a/src/pyams_utils/tests/__init__.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/tests/__init__.py	Sat Nov 23 14:51:46 2019 +0100
@@ -22,10 +22,10 @@
 import sys
 
 
-def get_package_dir(input):
+def get_package_dir(value):
     """Get package directory"""
 
-    package_dir = os.path.split(input)[0]
+    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_utils/tests/test_utilsdocs.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/tests/test_utilsdocs.py	Sat Nov 23 14:51:46 2019 +0100
@@ -29,7 +29,7 @@
 CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
 
 
-def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):
+def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):  # pylint: disable=invalid-name
     """Returns a test suite, based on doctests found in /doctest."""
     suite = []
     if globs is None:
@@ -61,4 +61,3 @@
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')
-
--- a/src/pyams_utils/tests/test_utilsdocstrings.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/tests/test_utilsdocstrings.py	Sat Nov 23 14:51:46 2019 +0100
@@ -46,7 +46,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:
--- a/src/pyams_utils/text.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/text.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.text module
+
+This module provides text manipulation and conversion functions, as well as a set of TALES
+extensions (see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`).
+"""
 
 import html
 
@@ -20,7 +24,6 @@
 from zope.interface import Interface
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
-from pyams_utils import _
 from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config
 from pyams_utils.interfaces.tales import ITALESExtension
 from pyams_utils.interfaces.text import IHTMLRenderer
@@ -28,14 +31,20 @@
 from pyams_utils.vocabulary import vocabulary_config
 
 
-def get_text_start(text, length, max=0):
+__docformat__ = 'restructuredtext'
+
+
+from pyams_utils import _
+
+
+def get_text_start(text, length, maxlen=0):
     """Get first words of given text with maximum given length
 
     If *max* is specified, text is shortened only if remaining text is longer this value
 
     :param str text: initial text
     :param integer length: maximum length of resulting text
-    :param integer max: if > 0, *text* is shortened only if remaining text is longer than max
+    :param integer maxlen: if > 0, *text* is shortened only if remaining text is longer than max
 
     >>> from pyams_utils.text import get_text_start
     >>> get_text_start('This is a long string', 10)
@@ -52,12 +61,13 @@
     text_length = len(result)
     while (index > 0) and (result[index] != ' '):
         index -= 1
-    if (index > 0) and (text_length > index + max):
+    if (index > 0) and (text_length > index + maxlen):
         return result[:index] + '&#133;'
     return text
 
 
-@adapter_config(name='truncate', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='truncate', context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
 class TruncateCharsTalesExtension(ContextRequestViewAdapter):
     """extension:truncate(value, length, max) TALES expression
 
@@ -67,10 +77,13 @@
     """
 
     @staticmethod
-    def render(value, length=50, max=0):
+    def render(value, length=50, maxlen=0):
+        """Render TALES extension;
+        see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
         if not value:
             return ''
-        return get_text_start(value, length, max=max)
+        return get_text_start(value, length, maxlen=maxlen)
 
 
 @adapter_config(name='raw', context=(str, IRequest), provides=IHTMLRenderer)
@@ -80,7 +93,8 @@
     This renderer renders input text 'as is', mainly for use in a <pre> tag.
     """
 
-    def render(self, **kwargs):
+    def render(self, **kwargs):  # pylint: disable=unused-argument
+        """Convert raw code as HTML"""
         return self.context
 
 
@@ -152,8 +166,8 @@
 
     Renderer name can be any registered HTML renderer adapter.
 
-    You can provide several renderers by giving their names separated by semicolon; renderers will then
-    act as in a pipe, each renderer transforming output of the previous one.
+    You can provide several renderers by giving their names separated by semicolon; renderers
+    will then act as in a pipe, each renderer transforming output of the previous one.
     """
     request = check_request()
     registry = request.registry
@@ -164,24 +178,28 @@
     return text
 
 
-empty_marker = object()
+EMPTY_MARKER = object()
 
 
 @adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension)
 class HTMLTalesExtension(ContextRequestViewAdapter):
     """*extension:html* TALES expression
 
-    If first *context* argument of the renderer is an object for which an :py:class:`IHTMLRenderer`
-    adapter can be found, this adapter is used to render the context to HTML; if *context* is a string,
-    it is converted to HTML using the renderer defined as second parameter; otherwise, context is just
-    converted to string using the :py:func:`str` function.
+    If first *context* argument of the renderer is an object for which an
+    :py:class:`IHTMLRenderer <pyams_utils.interfaces.text.IHTMLRenderer>`
+    adapter can be found, this adapter is used to render the context to HTML; if *context* is a
+    string, it is converted to HTML using the renderer defined as second parameter; otherwise,
+    context is just converted to string using the :py:func:`str` function.
 
-    You can provide several renderers by giving their names separated by semicolon; renderers will then
-    act as in a pipe, each renderer transforming output of the previous one.
+    You can provide several renderers by giving their names separated by semicolon; renderers
+    will then act as in a pipe, each renderer transforming output of the previous one.
     """
 
-    def render(self, context=empty_marker, renderer='text'):
-        if context is empty_marker:
+    def render(self, context=EMPTY_MARKER, renderer='text'):
+        """Render TALES extension;
+        see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
+        if context is EMPTY_MARKER:
             context = self.context
         if not context:
             return ''
@@ -191,10 +209,9 @@
             adapter = registry.queryMultiAdapter((context, self.request), IHTMLRenderer)
         if adapter is not None:
             return adapter.render()
-        elif isinstance(context, str):
+        if isinstance(context, str):
             return text_to_html(context, renderer)
-        else:
-            return str(context)
+        return str(context)
 
 
 PYAMS_HTML_RENDERERS_VOCABULARY = 'PyAMS HTML renderers'
@@ -204,7 +221,7 @@
 class RenderersVocabulary(SimpleVocabulary):
     """Text renderers vocabulary"""
 
-    def __init__(self, context=None):
+    def __init__(self, context=None):  # pylint: disable=unused-argument
         request = check_request()
         registry = request.registry
         translate = request.localizer.translate
@@ -226,12 +243,15 @@
 
     @staticmethod
     def render(value, css_class='', character='|', start_tag=None, end_tag=None):
+        """Render TALES extension;
+        see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
         if not value:
             return ''
-        br = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '')
+        br_tag = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '')
         elements = value.split(character)
         if start_tag:
             elements[0] = '<{0}>{1}</{0}>'.format(start_tag, elements[0])
         if end_tag:
             elements[-1] = '<{0}>{1}</{0}>'.format(end_tag, elements[-1])
-        return br.join(elements)
+        return br_tag.join(elements)
--- a/src/pyams_utils/timezone/__init__.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/timezone/__init__.py	Sat Nov 23 14:51:46 2019 +0100
@@ -39,7 +39,7 @@
 
 
 @adapter_config(context=IRequest, provides=ITZInfo)
-def tzinfo(request=None):
+def tzinfo(request=None):  # pylint: disable=unused-argument
     """request to timezone adapter
 
     There is no easy way to get timezone from a request.
--- a/src/pyams_utils/timezone/utility.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/timezone/utility.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,26 +10,27 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+"""PyAMS_utils.timezone.utility module
 
-# import interfaces
-from pyams_utils.interfaces.site import ISiteGenerations
-from pyams_utils.interfaces.timezone import IServerTimezone
+"""
 
-# import packages
 from persistent import Persistent
-from pyams_utils.registry import utility_config
-from pyams_utils.site import check_required_utilities
 from zope.container.contained import Contained
 from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
+from pyams_utils.interfaces.site import ISiteGenerations
+from pyams_utils.interfaces.timezone import IServerTimezone
+from pyams_utils.registry import utility_config
+from pyams_utils.site import check_required_utilities
+
+
+__docformat__ = 'restructuredtext'
+
 
 @implementer(IServerTimezone)
 class ServerTimezoneUtility(Persistent, Contained):
+    """Server timezone utility"""
 
     timezone = FieldProperty(IServerTimezone['timezone'])
 
@@ -38,12 +39,13 @@
 
 
 @utility_config(name='PyAMS timezone', provides=ISiteGenerations)
-class TimezoneGenerationsChecker(object):
+class TimezoneGenerationsChecker:
     """Timezone generations checker"""
 
     order = 10
     generation = 1
 
-    def evolve(self, site, current=None):
+    @staticmethod
+    def evolve(site, current=None):  # pylint: disable=unused-argument
         """Check for required utilities"""
         check_required_utilities(site, REQUIRED_UTILITIES)
--- a/src/pyams_utils/timezone/vocabulary.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/timezone/vocabulary.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,23 +10,24 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.timezone.vocabulary module
+
+This module provides a vocabulary of available timezones
+"""
+
+import pytz
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+
+from pyams_utils.vocabulary import vocabulary_config
 
 
-# import standard library
-import pytz
-
-# import interfaces
-
-# import packages
-from pyams_utils.vocabulary import vocabulary_config
-from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+__docformat__ = 'restructuredtext'
 
 
 @vocabulary_config(name='PyAMS timezones')
 class TimezonesVocabulary(SimpleVocabulary):
     """Timezones vocabulary"""
 
-    def __init__(self, *args, **kw):
+    def __init__(self, *args, **kw):  # pylint: disable=unused-argument
         terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
         super(TimezonesVocabulary, self).__init__(terms)
--- a/src/pyams_utils/traversing.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/traversing.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,7 +10,14 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.traversing module
+
+This module provides a custom Pyramid "namespace" traverser: using "++name++" URLs allows
+to traverse URLs based on custom traversing adapters.
+
+It also provides a "get_parent" function, which returns a parent object of given object providing
+a given interface.
+"""
 
 from pyramid.compat import decode_path_info, is_nonstr_iter
 from pyramid.exceptions import NotFound, URLDecodeError
@@ -28,6 +35,9 @@
 from pyams_utils.registry import query_utility
 
 
+__docformat__ = 'restructuredtext'
+
+
 class NamespaceTraverser(ResourceTreeTraverser):
     """Custom traverser handling views and namespaces
 
@@ -41,7 +51,7 @@
     NAMESPACE_SELECTOR = PLUS_SELECTOR * 2
 
     def __call__(self, request):
-
+        # pylint: disable=too-many-locals,too-many-branches,too-many-statements
         environ = request.environ
         matchdict = request.matchdict
 
@@ -68,8 +78,8 @@
             except KeyError:
                 # if environ['PATH_INFO'] is just not there
                 path = slash
-            except UnicodeDecodeError as e:
-                raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
+            except UnicodeDecodeError as exc:
+                raise URLDecodeError(exc.encoding, exc.object, exc.start, exc.end, exc.reason)
 
         if VH_ROOT_KEY in environ:
             # HTTP_X_VHM_ROOT
@@ -83,7 +93,7 @@
             vroot_idx = -1
 
         root = self.root
-        ob = vroot = root
+        obj = vroot = root
 
         request.registry.notify(BeforeTraverseEvent(root, request))
 
@@ -104,27 +114,28 @@
             vpath_tuple = split_path_info(vpath)
 
             for segment in vpath_tuple:
-                if ob is not root:
-                    request.registry.notify(BeforeTraverseEvent(ob, request))
+                if obj is not root:
+                    request.registry.notify(BeforeTraverseEvent(obj, request))
 
                 if segment == plus_selector:
                     # check for custom namespace called '+'
-                    # currently this namespace is used in PyAMS_default_theme package to get direct access to a given
-                    # content
+                    # currently this namespace is used in PyAMS_default_theme package to get
+                    # direct access to a given content
                     registry = get_current_registry()
-                    traverser = registry.queryMultiAdapter((ob, request), ITraversable, '+')
+                    traverser = registry.queryMultiAdapter((obj, request), ITraversable, '+')
                     if traverser is None:
                         raise NotFound()
                     try:
-                        ob = traverser.traverse(vpath_tuple[vroot_idx + i + 2], vpath_tuple[vroot_idx + i + 3:])
+                        obj = traverser.traverse(vpath_tuple[vroot_idx + i + 2],
+                                                 vpath_tuple[vroot_idx + i + 3:])
                     except IndexError:
-                        # the "+" namespace traverser is waiting for additional elements from input URL
-                        # so a "+" URL not followed by something else is just an error!
+                        # the "+" namespace traverser is waiting for additional elements from
+                        # input URL so a "+" URL not followed by something else is just an error!
                         raise NotFound()
                     else:
                         i += 1
                         return {
-                            'context': ob,
+                            'context': obj,
                             'view_name': ''.join(vpath_tuple[vroot_idx + i + 2:]),
                             'subpath': vpath_tuple[i + 2:],
                             'traversed': vpath_tuple[:vroot_idx + i + 2],
@@ -135,24 +146,25 @@
 
                 elif segment[:2] == ns_selector:
                     # check for namespace prefixed by '++'
-                    # when a namespace is detected, named "ITraversable" multi-adapters are searched for
-                    # context and request, or for context, sequentially; a NotFound exception is raised if traverser
-                    # can't be found, otherwise it's "traverse" method is called to get new context
-                    ns, name = segment[2:].split(ns_selector, 1)
+                    # when a namespace is detected, named "ITraversable" multi-adapters are
+                    # searched for context and request, or for context, sequentially; a NotFound
+                    # exception is raised if traverser can't be found, otherwise it's "traverse"
+                    # method is called to get new context
+                    nss, name = segment[2:].split(ns_selector, 1)
                     registry = get_current_registry()
-                    traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns)
+                    traverser = registry.queryMultiAdapter((obj, request), ITraversable, nss)
                     if traverser is None:
-                        traverser = registry.queryAdapter(ob, ITraversable, ns)
+                        traverser = registry.queryAdapter(obj, ITraversable, nss)
                     if traverser is None:
                         raise NotFound()
-                    ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
+                    obj = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
                     i += 1
                     continue
 
                 elif segment[:2] == view_selector:
                     # check for view name prefixed by '@@'
                     return {
-                        'context': ob,
+                        'context': obj,
                         'view_name': segment[2:],
                         'subpath': vpath_tuple[i + 1:],
                         'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -162,10 +174,10 @@
                     }
 
                 try:
-                    getitem = ob.__getitem__
+                    getitem = obj.__getitem__
                 except AttributeError:
                     return {
-                        'context': ob,
+                        'context': obj,
                         'view_name': segment,
                         'subpath': vpath_tuple[i + 1:],
                         'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -175,10 +187,10 @@
                     }
 
                 try:
-                    next = getitem(segment)
+                    next_item = getitem(segment)
                 except KeyError:
                     return {
-                        'context': ob,
+                        'context': obj,
                         'view_name': segment,
                         'subpath': vpath_tuple[i + 1:],
                         'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -187,15 +199,15 @@
                         'root': root
                     }
                 if i == vroot_idx:
-                    vroot = next
-                ob = next
+                    vroot = next_item
+                obj = next_item
                 i += 1
 
-        if ob is not root:
-            request.registry.notify(BeforeTraverseEvent(ob, request))
+        if obj is not root:
+            request.registry.notify(BeforeTraverseEvent(obj, request))
 
         return {
-            'context': ob,
+            'context': obj,
             'view_name': empty,
             'subpath': subpath,
             'traversed': vpath_tuple,
@@ -210,10 +222,10 @@
 
     :param object context: base element
     :param Interface interface: the interface that parend should implement
-    :param boolean allow_context: if 'True' (the default), traversing is done starting with context; otherwise,
-        traversing is done starting from context's parent
-    :param callable condition: an optional function that should return a 'True' result when called with parent
-        as first argument
+    :param boolean allow_context: if 'True' (the default), traversing is done starting with
+        context; otherwise, traversing is done starting from context's parent
+    :param callable condition: an optional function that should return a 'True' result when
+        called with parent as first argument
     """
     if allow_context:
         parent = context
@@ -238,6 +250,7 @@
 
     @property
     def parents(self):
+        """Get list of parents OIDs"""
         intids = query_utility(IIntIds)
         if intids is None:
             return []
--- a/src/pyams_utils/url.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/url.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,6 +10,19 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.url module
+
+This module provides several functions, adapters and TALES extensions which can be used to
+generate object's URLs.
+
+Three kinds of URLs can be used:
+ - an absolute URL, which is the standard way to access an object via it's physical path
+ - a canonical URL; this URL is the "preferred" one used to access an object, and is typically
+   used by search engines to index contents
+ - a relative URL; some contents can use this kind of URL to get access to an object from another
+   context.
+"""
+
 from pyramid.encode import url_quote, urlencode
 from pyramid.url import QUERY_SAFE, resource_url
 from pyramid_zope_request import PyramidPublisherRequest
@@ -38,18 +51,22 @@
     'this-is-my-test'
 
     Single letters are removed from generated URLs:
+
     >>> generate_url('This word has a single a')
     'this-word-has-single'
 
     But you can define the minimum length of word:
+
     >>> generate_url('This word has a single a', min_word_length=4)
     'this-word-single'
 
     If input text contains slashes, they are replaced with hyphens:
+
     >>> generate_url('This string contains/slash')
     'this-string-contains-slash'
 
     Punctation and special characters are completely removed:
+
     >>> generate_url('This is a string with a point. And why not?')
     'this-is-string-with-point-and-why-not'
     """
@@ -64,6 +81,12 @@
 #
 
 def get_display_context(request):
+    """Get current display context
+
+    The display context can be used when we generate a page to display an object in the context
+    of another one; PyAMS_content package is using this feature to display "shared" contents as
+    is they were located inside another site or folder...
+    """
     return request.annotations.get(DISPLAY_CONTEXT, request.context)
 
 
@@ -99,12 +122,12 @@
         else:
             result += '/' + view_name
     if query:
-        qs = ''
+        qstr = ''
         if isinstance(query, str):
-            qs = '?' + url_quote(query, QUERY_SAFE)
+            qstr = '?' + url_quote(query, QUERY_SAFE)
         elif query:
-            qs = '?' + urlencode(query, doseq=True)
-        result += qs
+            qstr = '?' + urlencode(query, doseq=True)
+        result += qstr
     return result
 
 
@@ -117,6 +140,9 @@
     """
 
     def render(self, context=None, view_name=None):
+        """Extension rendering; see
+        :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
         if context is None:
             context = self.context
         return absolute_url(context, self.request, view_name)
@@ -127,7 +153,11 @@
 #
 
 def canonical_url(context, request, view_name=None, query=None):
-    """Get resource canonical URL"""
+    """Get resource canonical URL
+
+    We look for an :py:class:`ICanonicalURL <pyams_utils.interfaces.url.ICanonicalURL>` adapter;
+    if none is found, we use the absolute_url.
+    """
 
     # if we pass a string to canonical_url(), argument is returned as-is!
     if isinstance(context, str):
@@ -139,8 +169,7 @@
 
     if url_adapter is not None:
         return url_adapter.get_url(view_name, query)
-    else:
-        return absolute_url(context, request, view_name, query)
+    return absolute_url(context, request, view_name, query)
 
 
 @adapter_config(name='canonical_url', context=(Interface, Interface, Interface),
@@ -152,6 +181,9 @@
     """
 
     def render(self, context=None, view_name=None):
+        """Render TALES extension; see
+        :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
         if context is None:
             context = self.context
         return canonical_url(context, self.request, view_name)
@@ -166,13 +198,15 @@
     """Default relative URL adapter"""
 
     def get_url(self, display_context=None, view_name=None, query=None):
+        # pylint: disable=unused-argument
+        """Default adapter returns absolute URL"""
         return absolute_url(self.context, self.request, view_name, query)
 
 
 def relative_url(context, request, display_context=None, view_name=None, query=None):
     """Get resource URL relative to given context"""
     if isinstance(request, PyramidPublisherRequest):
-        request = request._request
+        request = request._request  # pylint: disable=protected-access
     if display_context is None:
         display_context = request.annotations.get(DISPLAY_CONTEXT, request.context)
     adapter = request.registry.getMultiAdapter((context, request), IRelativeURL)
@@ -189,6 +223,9 @@
     """
 
     def render(self, context=None, view_name=None, query=None):
+        """Rander TALES extension;
+        see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+        """
         if context is None:
             context = self.context
         return relative_url(context, self.request, view_name=view_name, query=query)
--- a/src/pyams_utils/widget/decimal.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/widget/decimal.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,8 +10,10 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.widget.decimal module
 
+This module provides a custom data converter for decimal fields using dots as separator
+"""
 
 import decimal
 
@@ -21,6 +23,8 @@
 from pyams_utils.adapter import adapter_config
 from pyams_utils.schema import IDottedDecimalField
 
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
@@ -30,9 +34,6 @@
 
     errorMessage = _('The entered value is not a valid decimal literal.')
 
-    def __init__(self, field, widget):
-        super(DottedDecimalDataConverter, self).__init__(field, widget)
-
     def toWidgetValue(self, value):
         if not value:
             return self.field.missing_value
--- a/src/pyams_utils/wsgi.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/wsgi.py	Sat Nov 23 14:51:46 2019 +0100
@@ -10,13 +10,12 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-# import standard library
+"""PyAMS_utils.wsgi module
 
-# import interfaces
+This module provides a method decorator which can store it's value into request environment
+"""
 
-# import packages
+__docformat__ = 'restructuredtext'
 
 
 def wsgi_environ_cache(*names):
@@ -25,17 +24,20 @@
     :param [string...] names: keys to cache into environ; len(names) must
         be equal to the result's length or scalar
     """
-    def decorator(fn):
+
+    def decorator(func):
+
         def function_wrapper(self, request):
             scalar = len(names) == 1
             try:
-                rs = [request.environ[cached_key] for cached_key in names]
+                env = [request.environ[cached_key] for cached_key in names]
             except KeyError:
-                rs = fn(self, request)
+                env = func(self, request)
                 if scalar:
-                    rs = [rs, ]
-                request.environ.update(zip(names, rs))
-            return rs[0] if scalar else rs
+                    env = [env, ]
+                request.environ.update(zip(names, env))
+            return env[0] if scalar else env
+
         return function_wrapper
 
     return decorator
--- a/src/pyams_utils/zodb.py	Sat Nov 23 01:17:56 2019 +0100
+++ b/src/pyams_utils/zodb.py	Sat Nov 23 14:51:46 2019 +0100
@@ -83,7 +83,8 @@
     This object can be used to store all settings to be able to open a ZEO connection.
     Note that this class is required only for tasks specifically targeting a ZEO database
     connection (like a ZEO packer scheduler task); for generic ZODB operations, just use a
-    :class:`ZODBConnection` class defined through Pyramid's configuration file.
+    :py:class:`ZODBConnection <pyams_utils.zodb.ZODBConnection>` class defined through Pyramid's
+    configuration file.
 
     Note that a ZEO connection object is a context manager, so you can use it like this:
 
@@ -147,15 +148,15 @@
         :return: tuple containing ZEO client storage and DB object (if *get_storage* argument is
             set to *True*), or only DB object otherwise
         """
-        db = DB((self.server_name, self.server_port),
-                storage=self.storage,
-                username=self.username or '',
-                password=self.password or '',
-                realm=self.server_realm,
-                blob_dir=self.blob_dir,
-                shared_blob_dir=self.shared_blob_dir,
-                wait_timeout=wait_timeout)
-        return (db.storage, db) if get_storage else db
+        zdb = DB((self.server_name, self.server_port),
+                 storage=self.storage,
+                 username=self.username or '',
+                 password=self.password or '',
+                 realm=self.server_realm,
+                 blob_dir=self.blob_dir,
+                 shared_blob_dir=self.shared_blob_dir,
+                 wait_timeout=wait_timeout)
+        return (zdb.storage, zdb) if get_storage else zdb
 
     @property
     def connection(self):
@@ -209,8 +210,8 @@
     if settings is None:
         settings = get_global_registry().settings  # pylint: disable=no-member
     for name, uri in get_uris(settings):
-        db = db_from_uri(uri, name, {})
-        return db.open()
+        zdb = db_from_uri(uri, name, {})
+        return zdb.open()
 
 
 class ZODBConnection:
@@ -248,7 +249,7 @@
         return self._connection
 
     @property
-    def db(self):
+    def db(self):  # pylint: disable=invalid-name
         """Database getter"""
         return self._db
 
@@ -261,8 +262,8 @@
         """Load named connection matching registry settings"""
         for name, uri in get_uris(self.settings):
             if name == self.name:
-                db = db_from_uri(uri, name, {})
-                connection = self._connection = db.open()
+                zdb = db_from_uri(uri, name, {})
+                connection = self._connection = zdb.open()
                 self._db = connection.db()
                 self._storage = self.db.storage
                 return connection