Merge branch 'dev-tf-branch' into 'master'
authorFLORAC Thierry <thierry.florac@onf.fr>
Sat, 23 Nov 2019 00:38:44 +0100
changeset 424 5028ff294931
parent 423 d69d9c919448 (current diff)
parent 416 0ef63dbfecd1 (diff)
child 425 aeea1428f782
child 426 2022e4da3ad9
Merge branch 'dev-tf-branch' into 'master' Merge branche dev-tf See merge request onf/support/python/pyramid/pyams/pyams-utils!2
--- a/.gitlab-ci.yml	Wed Nov 20 19:40:26 2019 +0100
+++ b/.gitlab-ci.yml	Sat Nov 23 00:38:44 2019 +0100
@@ -1,8 +1,15 @@
 image: python:3.5
 
 stages:
-    - build
     - test
+    - dist
+    - quality
+
+cache:
+    paths:
+        - bin/
+        - eggs/
+        - parts/
 
 before_script:
     - export http_proxy=http://172.17.0.1:3128/
@@ -10,26 +17,48 @@
     - export https_proxy=http://172.17.0.1:3128/
     - export HTTPS_PROXY=http://172.17.0.1:3128/
 
-build:
-    stage: build
+bootstrap:
+    stage: .pre
     script:
         - python3.5 bootstrap.py --buildout-version=2.12.0
         - ./bin/buildout
+
+test:
+    stage: test
+    script:
+        - pwd
+        - ls -l
+        - head ./bin/test
         - ./bin/test
 
+dist:
+    stage: dist
+    script:
+        - ./bin/buildout setup setup.py clean --all sdist bdist_egg bdist_wheel
+    artifacts:
+        paths:
+            - ./dist
+
 pylint:
-    stage: test
+    stage: quality
     allow_failure: true
     script:
-        - pip install pylint --quiet
-        - pylint src/pyams_utils/
+        - pip install pylint-exit anybadge
+        - mkdir ./pylint
+        - ./bin/pylint src/pyams_utils/ | tee ./pylint/pylint.log || pylint-exit $?
+        - PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log)
+        - anybadge --label=Pylint --file=./pylint/pylint.svg --value=$PYLINT_SCORE 2=red 4=orange 8=yellow 10=green
+        - echo "Pylint score is $PYLINT_SCORE"
+    artifacts:
+        paths:
+            - ./pylint/
 
 quality:
-    stage: test
+    stage: quality
+    allow_failure: true
     image: docker:stable
     variables:
         DOCKER_DRIVER: overlay2
-    allow_failure: true
     services:
         - docker:stable-dind
     script:
--- a/.hgignore	Wed Nov 20 19:40:26 2019 +0100
+++ b/.hgignore	Sat Nov 23 00:38:44 2019 +0100
@@ -6,6 +6,8 @@
 syntax: regexp
 ^bin$
 syntax: regexp
+^eggs$
+syntax: regexp
 ^\.installed\.cfg$
 syntax: regexp
 ^\.settings$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.pylintrc	Sat Nov 23 00:38:44 2019 +0100
@@ -0,0 +1,407 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=2
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality. This option is deprecated
+# and it will be removed in Pylint 2.0.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=import-star-module-level,old-octal-literal,oct-method,inherit-non-class,logging-format-interpolation,too-many-ancestors,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]". This option is deprecated
+# and it will be removed in Pylint 2.0.
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Number of spaces of indent required inside a hanging  or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=10
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=yes
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,future.builtins
+
+
+[BASIC]
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
--- a/buildout.cfg	Wed Nov 20 19:40:26 2019 +0100
+++ b/buildout.cfg	Sat Nov 23 00:38:44 2019 +0100
@@ -1,5 +1,5 @@
 [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
@@ -27,43 +27,12 @@
     package
     i18n
     pyflakes
+    pylint
     test
 
 [package]
 recipe = zc.recipe.egg
-eggs =
-    babel
-    BTrees
-    chameleon
-    docutils
-    httplib2
-    persistent
-    pyams_utils
-    pyramid
-    pyramid_zodbconn
-    pysocks
-    pytz
-    transaction
-    z3c.form
-    z3c.pt
-    z3c.ptcompat
-    ZEO
-    ZODB
-    zope.annotation
-    zope.component
-    zope.container
-    zope.contentprovider
-    zope.datetime
-    zope.dublincore
-    zope.interface
-    zope.intid
-    zope.keyreference
-    zope.lifecycleevent
-    zope.location
-    zope.publisher
-    zope.schema
-    zope.site
-    zope.traversing
+eggs = pyams_utils
 interpreter = py
 
 [i18n]
@@ -84,9 +53,15 @@
 on_install = true
 cmds = ${buildout:develop}/bin/${pyflakes:scripts}
 
+[pylint]
+recipe = zc.recipe.egg
+eggs = pylint
+entry-points = pylint=pylint.lint:Run
+arguments = sys.argv[1:]
+
 [test]
 recipe = zc.recipe.testrunner
 eggs = pyams_utils [test]
 
 [versions]
-pyams_utils = 0.1.34.2
+pyams_utils = 0.1.35
--- a/docs/HISTORY.txt	Wed Nov 20 19:40:26 2019 +0100
+++ b/docs/HISTORY.txt	Sat Nov 23 00:38:44 2019 +0100
@@ -1,6 +1,10 @@
 Changelog
 =========
 
+0.1.35
+------
+ - Pylint code cleanup and GitLab-CI integration updates
+
 0.1.34.2
 --------
  - correction in "generate_url" function
--- a/setup.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/setup.py	Sat Nov 23 00:38:44 2019 +0100
@@ -25,7 +25,7 @@
 README = os.path.join(DOCS, 'README.txt')
 HISTORY = os.path.join(DOCS, 'HISTORY.txt')
 
-version = '0.1.34.2'
+version = '0.1.35'
 long_description = open(README).read() + '\n\n' + open(HISTORY).read()
 
 tests_require = [
@@ -65,28 +65,38 @@
           # -*- Extra requirements: -*-
           'babel',
           'beaker',
+          'BTrees',
           'chameleon',
           'docutils',
+          'fanstatic',
           'httplib2',
           'markdown',
           'persistent',
+          'pygments',
           'pyramid',
           'pyramid_zodbconn',
           'pyramid_zope_request',
           'pysocks',
           'pytz',
           'transaction',
+          'venusian',
           'z3c.form',
           'z3c.pt',
           'z3c.ptcompat',
+          'ZEO',
           'ZODB',
           'zope.annotation',
           'zope.component',
           'zope.container',
-          'zope.dublincore',
+          'zope.contentprovider',
           'zope.datetime',
+          'zope.dublincore',
           'zope.interface',
+          'zope.intid',
+          'zope.keyreference',
+          'zope.lifecycleevent',
           'zope.location',
+          'zope.publisher',
           'zope.schema',
           'zope.site',
           'zope.traversing'
--- a/src/pyams_utils.egg-info/SOURCES.txt	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils.egg-info/SOURCES.txt	Sat Nov 23 00:38:44 2019 +0100
@@ -51,6 +51,7 @@
 src/pyams_utils.egg-info/top_level.txt
 src/pyams_utils/doctests/README.txt
 src/pyams_utils/doctests/dates.txt
+src/pyams_utils/doctests/inherit.txt
 src/pyams_utils/doctests/request.txt
 src/pyams_utils/doctests/unicode.txt
 src/pyams_utils/interfaces/__init__.py
--- a/src/pyams_utils.egg-info/requires.txt	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils.egg-info/requires.txt	Sat Nov 23 00:38:44 2019 +0100
@@ -1,27 +1,38 @@
 setuptools
 babel
 beaker
+BTrees
 chameleon
 docutils
+fanstatic
 httplib2
 markdown
 persistent
+pygments
 pyramid
 pyramid_zodbconn
+pyramid_zope_request
 pysocks
 pytz
 transaction
+venusian
 z3c.form
 z3c.pt
 z3c.ptcompat
+ZEO
 ZODB
 zope.annotation
 zope.component
 zope.container
-zope.dublincore
+zope.contentprovider
 zope.datetime
+zope.dublincore
 zope.interface
+zope.intid
+zope.keyreference
+zope.lifecycleevent
 zope.location
+zope.publisher
 zope.schema
 zope.site
 zope.traversing
--- a/src/pyams_utils/__init__.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/__init__.py	Sat Nov 23 00:38:44 2019 +0100
@@ -18,24 +18,23 @@
 custom decorators to define several kinds of properties, and so on.
 """
 
+from pyramid.i18n import TranslationStringFactory
+from zope.schema.fieldproperty import FieldProperty
+
+
 __docformat__ = 'restructuredtext'
 
-from zope.schema.fieldproperty import FieldProperty
-
-from pyramid.i18n import TranslationStringFactory
 _ = TranslationStringFactory('pyams_utils')
 
 
 def get_field_doc(self):
     """Try to get FieldProperty field docstring from field interface"""
-    field = self._FieldProperty__field
+    field = self._FieldProperty__field  # pylint: disable=protected-access
     if field.title:
         if field.description:
             return '{0}: {1}'.format(field.title, field.description)
-        else:
-            return field.title
-    else:
-        return super(self.__class__, self).__doc__
+        return field.title
+    return super(self.__class__, self).__doc__
 
 
 FieldProperty.__doc__ = property(get_field_doc)
@@ -43,5 +42,5 @@
 
 def includeme(config):
     """pyams_utils features include"""
-    from .include import include_package
+    from .include import include_package  # pylint: disable=import-outside-toplevel
     include_package(config)
--- a/src/pyams_utils/adapter.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/adapter.py	Sat Nov 23 00:38:44 2019 +0100
@@ -12,8 +12,8 @@
 
 """Adapters management package
 
-This package provides a small set of standard base adapters for *context*, *context* and *request*, and
-*context* and *request* and *view*.
+This package provides a small set of standard base adapters for *context*, *context* and *request*,
+and *context* and *request* and *view*.
 
 See :ref:`zca` to see how PyAMS can help components management.
 """
@@ -27,22 +27,22 @@
 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
+from pyams_utils.registry import get_current_registry, get_global_registry
 
 
 __docformat__ = 'restructuredtext'
 
-logger = logging.getLogger('PyAMS (utils)')
+LOGGER = logging.getLogger('PyAMS (utils)')
 
 
-class ContextAdapter(object):
+class ContextAdapter:
     """Context adapter"""
 
     def __init__(self, context):
         self.context = context
 
 
-class ContextRequestAdapter(object):
+class ContextRequestAdapter:
     """Context + request multi-adapter"""
 
     def __init__(self, context, request):
@@ -50,7 +50,7 @@
         self.request = request
 
 
-class ContextRequestViewAdapter(object):
+class ContextRequestViewAdapter:
     """Context + request + view multi-adapter"""
 
     def __init__(self, context, request, view):
@@ -59,17 +59,17 @@
         self.view = view
 
 
-class NullAdapter(object):
+class NullAdapter:
     """An adapter which always return None!
 
     Can be useful to override a default adapter...
     """
 
-    def __new__(cls, *arsg, **kwargs):
+    def __new__(cls, *args, **kwargs):  # pylint: disable=unused-argument
         return None
 
 
-class adapter_config(object):
+class adapter_config:    # pylint: disable=invalid-name
     """Function or class decorator to declare an adapter
 
     Annotation parameters can be:
@@ -77,6 +77,7 @@
     :param str='' name: name of the adapter
     :param [Interface...] context: an interface, or a tuple of interfaces, that the component adapts
     :param Interface provides: the interface that the adapter provides
+    :param registry: the registry into which adapter registration should be made
     """
 
     venusian = venusian
@@ -91,48 +92,49 @@
         settings = self.__dict__.copy()
         depth = settings.pop('_depth', 0)
 
-        def callback(context, name, ob):
+        def callback(context, name, obj):
             adapts = settings.get('context')
             if adapts is None:
-                adapts = getattr(ob, '__component_adapts__', None)
+                adapts = getattr(obj, '__component_adapts__', None)
                 if adapts is None:
                     raise TypeError("No for argument was provided for %r and "
-                                    "can't determine what the factory adapts." % ob)
+                                    "can't determine what the factory adapts." % obj)
             if not isinstance(adapts, tuple):
                 adapts = (adapts,)
 
             provides = settings.get('provides')
             if provides is None:
-                intfs = list(implementedBy(ob))
+                intfs = list(implementedBy(obj))
                 if len(intfs) == 1:
                     provides = intfs[0]
                 if provides is None:
                     raise TypeError("Missing 'provides' argument")
 
-            config = context.config.with_package(info.module)
-            logger.debug("Registering adapter {0} for {1} providing {2}".format(str(ob),
-                                                                                str(adapts),
-                                                                                str(provides)))
-            config.registry.registerAdapter(ob, adapts, provides, settings.get('name', ''))
+            config = context.config.with_package(info.module)  # pylint: disable=no-member
+            LOGGER.debug("Registering adapter %s for %s providing %s",
+                         str(obj), str(adapts), str(provides))
+            registry = settings.get('registry', config.registry)
+            registry.registerAdapter(obj, adapts, provides, settings.get('name', ''))
 
         info = self.venusian.attach(wrapped, callback, category='pyams_adapter',
                                     depth=depth + 1)
 
-        if info.scope == 'class':
+        if info.scope == 'class':  # pylint: disable=no-member
             # if the decorator was attached to a method in a class, or
             # otherwise executed at class scope, we need to set an
             # 'attr' into the settings if one isn't already in there
             if settings.get('attr') is None:
                 settings['attr'] = wrapped.__name__
 
-        settings['_info'] = info.codeinfo  # fbo "action_method"
+        settings['_info'] = info.codeinfo  # pylint: disable=no-member
         return wrapped
 
 
 def get_annotation_adapter(context, key, factory=None, markers=None, notify=True,
                            locate=True, parent=None, name=None, callback=None, **kwargs):
+    # pylint: disable=too-many-arguments
     """Get an adapter via object's annotations, creating it if not existent
-    
+
     :param object context: context object which should be adapted
     :param str key: annotations key to look for
     :param factory: if annotations key is not found, this is the factory which will be used to
@@ -151,28 +153,27 @@
     annotations = IAnnotations(context, None)
     if annotations is None:
         return None
-    adapter = annotations.get(key)
+    adapter = annotations.get(key)  # pylint: disable=assignment-from-no-return
     if adapter is None:
         if 'default' in kwargs:
             return kwargs['default']
-        elif factory is None:
+        if factory is None:
             return None
-        else:
-            if is_interface(factory):
-                factory = get_object_factory(factory)
-                assert factory is not None, "Missing object factory"
-            adapter = annotations[key] = factory()
-            if markers:
-                if not isinstance(markers, (list, tuple, set)):
-                    markers = {markers}
-                for marker in markers:
-                    alsoProvides(adapter, marker)
-            if notify:
-                get_current_registry().notify(ObjectCreatedEvent(adapter))
-            if locate:
-                zope_locate(adapter, context if parent is None else parent, name)
-            if callback:
-                callback(adapter)
+        if is_interface(factory):
+            factory = get_object_factory(factory)
+            assert factory is not None, "Missing object factory"
+        adapter = annotations[key] = factory()
+        if markers:
+            if not isinstance(markers, (list, tuple, set)):
+                markers = {markers}
+            for marker in markers:
+                alsoProvides(adapter, marker)
+        if notify:
+            get_current_registry().notify(ObjectCreatedEvent(adapter))
+        if locate:
+            zope_locate(adapter, context if parent is None else parent, name)
+        if callback:
+            callback(adapter)
     return adapter
 
 
--- a/src/pyams_utils/attr.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/attr.py	Sat Nov 23 00:38:44 2019 +0100
@@ -17,8 +17,6 @@
 This adapter is actually used to get access to 'file' attributes in PyAMS_file package.
 """
 
-__docformat__ = 'restructuredtext'
-
 from pyramid.exceptions import NotFound
 from zope.interface import Interface
 from zope.traversing.interfaces import ITraversable
@@ -26,6 +24,9 @@
 from pyams_utils.adapter import ContextAdapter, adapter_config
 
 
+__docformat__ = 'restructuredtext'
+
+
 @adapter_config(name='attr', context=Interface, provides=ITraversable)
 class AttributeTraverser(ContextAdapter):
     """++attr++ namespace traverser
@@ -38,7 +39,8 @@
     Where *name* is the name of the requested attribute.
     """
 
-    def traverse(self, name, furtherpath=None):
+    def traverse(self, name, furtherpath=None):  # pylint: disable=unused-argument
+        """Traverse from current context to given attribute"""
         if '.' in name:
             name = name.split('.', 1)
         try:
--- a/src/pyams_utils/cache.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/cache.py	Sat Nov 23 00:38:44 2019 +0100
@@ -21,8 +21,6 @@
 A TALES helper extension is also provided to get an object's cache key from a Chameleon template.
 """
 
-__docformat__ = 'restructuredtext'
-
 from persistent.interfaces import IPersistent
 from zope.interface import Interface
 
@@ -31,25 +29,32 @@
 from pyams_utils.interfaces.tales import ITALESExtension
 
 
+__docformat__ = 'restructuredtext'
+
+
 @adapter_config(context=object, provides=ICacheKeyValue)
 def object_cache_key_adapter(obj):
+    """Cache key adapter for any object"""
     return str(id(obj))
 
 
 @adapter_config(context=str, provides=ICacheKeyValue)
 def string_cache_key_adapter(obj):
+    """Cache key adapter for string value"""
     return obj
 
 
 @adapter_config(context=IPersistent, provides=ICacheKeyValue)
 def persistent_cache_key_adapter(obj):
+    """Cache key adapter for persistent object"""
+    # pylint: disable=protected-access
     if obj._p_oid:
         return str(int.from_bytes(obj._p_oid, byteorder='big'))
-    else:  # unsaved object
-        return str(id(obj))
+    return str(id(obj))
 
 
-@adapter_config(name='cache_key', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='cache_key', context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
 class CacheKeyTalesExtension(ContextRequestViewAdapter):
     """extension:cache_key(context) TALES extension
 
@@ -57,6 +62,7 @@
     """
 
     def render(self, context=None):
+        """Rendering of TALES extension"""
         if context is None:
             context = self.request.context
         return ICacheKeyValue(context)
--- a/src/pyams_utils/container.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/container.py	Sat Nov 23 00:38:44 2019 +0100
@@ -9,14 +9,13 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+# pylint: disable=no-name-in-module
 
 """PyAMS_utils.container module
 
 This module provides several classes, adapters and functions about containers.
 """
 
-__docformat__ = 'restructuredtext'
-
 from BTrees.OOBTree import OOBTree
 from persistent.list import PersistentList
 from pyramid.threadlocal import get_current_registry
@@ -28,6 +27,9 @@
 from pyams_utils.adapter import ContextAdapter, adapter_config
 
 
+__docformat__ = 'restructuredtext'
+
+
 class BTreeOrderedContainer(OrderedContainer):
     """BTree based ordered container
 
@@ -35,11 +37,12 @@
     """
 
     def __init__(self):
+        # pylint: disable=super-init-not-called
         self._data = OOBTree()
         self._order = PersistentList()
 
 
-class ParentSelector(object):
+class ParentSelector:
     """Interface based parent selector
 
     This selector can be used as a subscriber predicate on IObjectAddedEvent to define
@@ -55,11 +58,13 @@
     """
 
     def __init__(self, ifaces, config):
+        # pylint: disable=unused-argument
         if not isinstance(ifaces, (list, tuple, set)):
             ifaces = (ifaces,)
         self.interfaces = ifaces
 
     def text(self):
+        """Predicate string output"""
         return 'parent_selector = %s' % str(self.interfaces)
 
     phash = text
@@ -119,7 +124,7 @@
         yield root
     locations = ISublocations(root, None)
     if locations is not None:
-        for location in locations.sublocations():
+        for location in locations.sublocations():  # pylint: disable=too-many-function-args
             if condition(location):
                 yield location
             yield from find_objects_matching(location, condition, ignore_root=True)
--- a/src/pyams_utils/context.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/context.py	Sat Nov 23 00:38:44 2019 +0100
@@ -18,10 +18,39 @@
 interface).
 """
 
+import sys
+from io import StringIO
+
+from contextlib import contextmanager
+
 __docformat__ = 'restructuredtext'
 
 
-class ContextSelector(object):
+@contextmanager
+def capture(func, *args, **kwargs):
+    """Context manager used to capture standard output"""
+    out, sys.stdout = sys.stdout, StringIO()
+    try:
+        func(*args, **kwargs)
+        sys.stdout.seek(0)
+        yield sys.stdout.read()
+    finally:
+        sys.stdout = out
+
+
+@contextmanager
+def capture_stderr(func, *args, **kwargs):
+    """Context manager used to capture error output"""
+    err, sys.stderr = sys.stderr, StringIO()
+    try:
+        func(*args, **kwargs)
+        sys.stderr.seek(0)
+        yield sys.stderr.read()
+    finally:
+        sys.stderr = err
+
+
+class ContextSelector(object):  # 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
@@ -37,7 +66,7 @@
             '''This is an event handler for an ISiteRoot object modification event'''
     """
 
-    def __init__(self, ifaces, config):
+    def __init__(self, ifaces, config):  # pylint: disable=unused-argument
         if not isinstance(ifaces, (list, tuple, set)):
             ifaces = (ifaces,)
         self.interfaces = ifaces
--- a/src/pyams_utils/data.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/data.py	Sat Nov 23 00:38:44 2019 +0100
@@ -12,10 +12,10 @@
 
 """PyAMS_utils.data module
 
-The *IObjectData* interface is a generic interface which can be used to assign custom data to any object.
-This object data may be any object which can be serialized to JSON, and assigned to any HTML *data* attribute.
-It can typically be used to set a *data-ams-data* attribute to objects, which is afterwards converted to
-classic *data-* attributes by **MyAMS.js** framework.
+The *IObjectData* interface is a generic interface which can be used to assign custom data to any
+object. This object data may be any object which can be serialized to JSON, and assigned to any
+HTML *data* attribute. It can typically be used to set a *data-ams-data* attribute to objects,
+which is afterwards converted to classic *data-* attributes by **MyAMS.js** framework.
 
 For example, for a custom widget in a form::
 
@@ -34,8 +34,6 @@
     <div data-ams-colorpicker-position="top left">...</div>
 """
 
-__docformat__ = 'restructuredtext'
-
 import json
 
 from pyramid.interfaces import IRequest
@@ -47,6 +45,9 @@
 from pyams_utils.interfaces.tales import ITALESExtension
 
 
+__docformat__ = 'restructuredtext'
+
+
 @adapter_config(context=IObjectData, provides=IObjectDataRenderer)
 class ObjectDataRenderer(ContextAdapter):
     """Object data JSON renderer"""
@@ -57,12 +58,14 @@
         return json.dumps(data.object_data) if data is not None else None
 
 
-@adapter_config(name='object_data', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='object_data', context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
 class ObjectDataExtension(ContextRequestViewAdapter):
     """extension:object_data TALES extension
 
     This TALES extension is to be used in Chameleon templates to define a custom data attribute
-    which stores all object data (see :py:class:`pyams_utils.interfaces.data.IObjectData` interface), like this::
+    which stores all object data (see :py:class:`pyams_utils.interfaces.data.IObjectData`
+    interface), like this::
 
         <div tal:attributes="data-ams-data extension:object_data(context)">...</div>
     """
@@ -74,13 +77,16 @@
         renderer = IObjectDataRenderer(context, None)
         if renderer is not None:
             return renderer.get_object_data()
+        return None
 
 
-@adapter_config(name='request_data', context=(Interface, IRequest, Interface), provides=ITALESExtension)
+@adapter_config(name='request_data', context=(Interface, IRequest, Interface),
+                provides=ITALESExtension)
 class PyramidRequestDataExtension(ContextRequestViewAdapter):
     """extension:request_data TALES extension for Pyramid request
 
-    This TALES extension can be used to get a request data, previously stored in the request via an annotation.
+    This TALES extension can be used to get a request data, previously stored in the request via
+    an annotation.
     For example::
 
         <div tal:content="extension:request_data('my.annotation.key')">...</div>
@@ -91,11 +97,13 @@
         return self.request.annotations.get(params)
 
 
-@adapter_config(name='request_data', context=(Interface, IBrowserRequest, Interface), provides=ITALESExtension)
+@adapter_config(name='request_data', context=(Interface, IBrowserRequest, Interface),
+                provides=ITALESExtension)
 class BrowserRequestDataExtension(ContextRequestViewAdapter):
     """extension:request_data TALES extension for Zope browser request
 
-    This TALES extension can be used to get a request data, previously stored in the request via an annotation.
+    This TALES extension can be used to get a request data, previously stored in the request via
+    an annotation.
     For example::
 
         <div tal:content="extension:request_data('my.annotation.key')">...</div>
--- a/src/pyams_utils/date.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/date.py	Sat Nov 23 00:38:44 2019 +0100
@@ -16,8 +16,6 @@
 dates and datetimes.
 """
 
-__docformat__ = 'restructuredtext'
-
 from datetime import datetime
 
 from zope.datetime import parseDatetimetz
@@ -29,14 +27,17 @@
 from pyams_utils.request import check_request
 from pyams_utils.timezone import gmtime, tztime
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
 def unidate(value):
     """Get specified date converted to unicode ISO format
-    
+
     Dates are always assumed to be stored in GMT timezone
-    
+
     :param date value: input date to convert to unicode
     :return: unicode; input date converted to unicode
 
@@ -54,9 +55,9 @@
 
 def parse_date(value):
     """Get date specified in unicode ISO format to Python datetime object
-    
+
     Dates are always assumed to be stored in GMT timezone
-    
+
     :param str value: unicode date to be parsed
     :return: datetime; the specified value, converted to datetime
 
@@ -71,7 +72,7 @@
 
 def date_to_datetime(value):
     """Get datetime value converted from a date or datetime object
-    
+
     :param date/datetime value: a date or datetime value to convert
     :return: datetime; input value converted to datetime
 
@@ -88,7 +89,7 @@
     """
     if not value:
         return None
-    if type(value) is datetime:
+    if isinstance(value, datetime):
         return value
     return datetime(value.year, value.month, value.day)
 
@@ -100,11 +101,11 @@
 EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
 
 
-def format_date(value, format=EXT_DATE_FORMAT, request=None):
+def format_date(value, format_string=EXT_DATE_FORMAT, request=None):
     """Format given date with the given format
 
     :param datetime value: the value to format
-    :param str format: a format string to use by `strftime` function
+    :param str format_string: a format string to use by `strftime` function
     :param request: the request from which to extract localization info for translation
     :return: str; input datetime converted to given format
 
@@ -121,14 +122,14 @@
     if request is None:
         request = check_request()
     localizer = request.localizer
-    return datetime.strftime(tztime(value), localizer.translate(format))
+    return datetime.strftime(tztime(value), localizer.translate(format_string))
 
 
-def format_datetime(value, format=EXT_DATETIME_FORMAT, request=None):
+def format_datetime(value, format_string=EXT_DATETIME_FORMAT, request=None):
     """Format given datetime with the given format including time
 
     :param datetime value: the value to format
-    :param str format: a format string to use by `strftime` function
+    :param str format_string: a format string to use by `strftime` function
     :param request: request; the request from which to extract localization info for translation
     :return: str; input datetime converted to given format
 
@@ -140,11 +141,12 @@
     >>> format_datetime(value, SH_DATETIME_FORMAT)
     '15/11/2016 - 10:13'
     """
-    return format_date(value, format, request)
+    return format_date(value, format_string, request)
 
 
 def get_age(value, request=None):
-    """Get 'human' age of a given datetime (including timezone) compared to current datetime (in UTC)
+    """Get 'human' age of a given datetime (including timezone) compared to current datetime
+    (in UTC)
 
     :param datetime value: input datetime to be compared with current datetime
     :return: str; the delta value, converted to months, weeks, days, hours or minutes
@@ -155,30 +157,31 @@
     now = gmtime(datetime.utcnow())
     delta = now - gmtime(value)
     if delta.days > 60:
-        return translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
+        result = translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
     elif delta.days > 10:
-        return translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
+        result = translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
     elif delta.days > 2:
-        return translate(_("%d days ago")) % delta.days
+        result = translate(_("%d days ago")) % delta.days
     elif delta.days == 2:
-        return translate(_("the day before yesterday"))
+        result = translate(_("the day before yesterday"))
     elif delta.days == 1:
-        return translate(_("yesterday"))
-    else:
+        result = translate(_("yesterday"))
+    else:  # less than one day
         hours = int(round(delta.seconds * 1.0 / 3600))
         if hours > 1:
-            return translate(_("%d hours ago")) % hours
+            result = translate(_("%d hours ago")) % hours
         elif delta.seconds > 300:
-            return translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
+            result = translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
         else:
-            return translate(_("less than 5 minutes ago"))
+            result = translate(_("less than 5 minutes ago"))
+    return result
 
 
-def get_duration(v1, v2=None, request=None):
+def get_duration(first, last=None, request=None):  # pylint: disable=too-many-branches
     """Get 'human' delta as string between two dates
 
-    :param datetime v1: start date
-    :param datetime v2: end date, or current date (in UTC) if None
+    :param datetime first: start date
+    :param datetime last: end date, or current date (in UTC) if None
     :param request: the request from which to extract localization infos
     :return: str; approximate delta between the two input dates
 
@@ -221,39 +224,41 @@
     >>> get_duration(date1, date2, request)
     '15 seconds'
     """
-    if v2 is None:
-        v2 = datetime.utcnow()
-    assert isinstance(v1, datetime) and isinstance(v2, datetime)
+    if last is None:
+        last = datetime.utcnow()
+    assert isinstance(first, datetime) and isinstance(last, datetime)
     if request is None:
         request = check_request()
     translate = request.localizer.translate
-    v1, v2 = min(v1, v2), max(v1, v2)
-    delta = v2 - v1
+    first, last = min(first, last), max(first, last)
+    delta = last - first
     if delta.days > 60:
-        return translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
+        result = translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
     elif delta.days > 10:
-        return translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
+        result = translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
     elif delta.days >= 2:
-        return translate(_("%d days")) % delta.days
+        result = translate(_("%d days")) % delta.days
     else:
         hours = int(round(delta.seconds * 1.0 / 3600))
         if delta.days == 1:
             if hours == 0:
-                return translate(_("24 hours"))
+                result = translate(_("24 hours"))
             else:
-                return translate(_("%d day and %d hours")) % (delta.days, hours)
+                result = translate(_("%d day and %d hours")) % (delta.days, hours)
         else:
             if hours > 2:
-                return translate(_("%d hours")) % hours
+                result = translate(_("%d hours")) % hours
             else:
                 minutes = int(round(delta.seconds * 1.0 / 60))
                 if minutes > 2:
-                    return translate(_("%d minutes")) % minutes
+                    result = translate(_("%d minutes")) % minutes
                 else:
-                    return translate(_("%d seconds")) % delta.seconds
+                    result = translate(_("%d seconds")) % delta.seconds
+    return result
 
 
-@adapter_config(name='timestamp', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='timestamp', context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
 class TimestampTalesAdapter(ContextRequestViewAdapter):
     """extension:timestamp(context) TALES adapter
 
@@ -261,14 +266,14 @@
     """
 
     def render(self, context=None, formatting=None):
+        """Render TALES extension"""
         if context is None:
             context = self.request.context
         if formatting == 'iso':
             format_func = datetime.isoformat
         else:
             format_func = datetime.timestamp
-        dc = IZopeDublinCore(context, None)
-        if dc is None:
-            return format_func(tztime(datetime.utcnow()))
-        else:
-            return format_func(tztime(dc.modified))
+        zdc = IZopeDublinCore(context, None)
+        if zdc is None:
+            return format_func(tztime(zdc.modified))
+        return format_func(tztime(datetime.utcnow()))
--- a/src/pyams_utils/decorator.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/decorator.py	Sat Nov 23 00:38:44 2019 +0100
@@ -16,22 +16,43 @@
 deprecated.
 """
 
-__docformat__ = 'restructuredtext'
-
 import functools
 import warnings
 
+__docformat__ = 'restructuredtext'
+
 
 def deprecated(*msg):
     """This is a decorator which can be used to mark functions as deprecated.
 
     It will result in a warning being emitted when the function is used.
+
+    >>> from pyams_utils.context import capture_stderr
+    >>> from pyams_utils.decorator import deprecated
+
+    >>> @deprecated
+    ... def my_function(value):
+    ...     return value
+
+    >>> with capture_stderr(my_function, 1) as err:
+    ...     print(err.split('\\n')[0])
+    <doctest ... DeprecationWarning: Function my_function is deprecated.
+
+    >>> @deprecated('Deprecation message')
+    ... def my_function_2(value):
+    ...     return value
+
+    >>> with capture_stderr(my_function_2, 2) as err:
+    ...     print(err.split('\\n')[0])
+    <doctest ... DeprecationWarning: Function my_function_2 is deprecated. Deprecation message
     """
 
     def decorator(func):
+        """Actual decorator"""
 
         @functools.wraps(func)
         def new_func(*args, **kwargs):
+            """Wrapped decorator function"""
             warnings.warn_explicit("Function %s is deprecated. %s" % (func.__name__, message),
                                    category=DeprecationWarning,
                                    filename=func.__code__.co_filename,
--- a/src/pyams_utils/encoding.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/encoding.py	Sat Nov 23 00:38:44 2019 +0100
@@ -12,11 +12,9 @@
 
 """PyAMS_utils.encoding module
 
-This module defines a vocabulary of available encodings, as well as an "encoding" field.
+This module defines a vocabulary of available encodings, as well as an "encoding" choice field.
 """
 
-__docformat__ = 'restructuredtext'
-
 from zope.interface import implementer
 from zope.schema import Choice
 from zope.schema.interfaces import IChoice
@@ -25,6 +23,9 @@
 from pyams_utils.request import check_request
 from pyams_utils.vocabulary import vocabulary_config
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
@@ -136,7 +137,7 @@
         super(EncodingsVocabulary, self).__init__(terms, *interfaces)
 
 
-class IEncodingField(IChoice):
+class IEncodingField(IChoice):  # pylint: disable=too-many-ancestors
     """Encoding field interface"""
 
 
--- a/src/pyams_utils/factory.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/factory.py	Sat Nov 23 00:38:44 2019 +0100
@@ -15,7 +15,7 @@
 This module provides a decorator and a small set of functions to handle object factories.
 
 Instead of directly using a class as an object factory, the object of this module is to
-let you create an object based on an interface. The first step is to create an object 
+let you create an object based on an interface. The first step is to create an object
 implementing this interface, and then to register it as a factory:
 
 .. code-block:: python
@@ -23,9 +23,9 @@
     @implementer(IMyInterface)
     class MyClass(object):
         '''Class implementing my interface'''
-    
+
     register_factory(IMyInterface, MyClass)
-    
+
 Factory registry can also be handle by a decorator called "factory_config":
 
 .. code-block:: python
@@ -51,8 +51,6 @@
 own implementation of any PyAMS interface handled by factories.
 """
 
-__docformat__ = 'restructuredtext'
-
 import logging
 
 import venusian
@@ -63,12 +61,16 @@
 from pyams_utils.interfaces import IObjectFactory
 from pyams_utils.registry import get_global_registry
 
-logger = logging.getLogger('PyAMS (utils)')
+
+__docformat__ = 'restructuredtext'
 
 
-def is_interface(object):
+LOGGER = logging.getLogger('PyAMS (utils)')
+
+
+def is_interface(obj):
     """Check if given object is an interface"""
-    return issubclass(object.__class__, InterfaceClass)
+    return issubclass(obj.__class__, InterfaceClass)
 
 
 def get_interface_name(iface):
@@ -78,7 +80,7 @@
 
 @adapter(Interface)
 @implementer(IObjectFactory)
-class ObjectFactoryAdapter(object):
+class ObjectFactoryAdapter:
     """Most basic object factory adapter"""
 
     factory = None
@@ -87,7 +89,7 @@
         self.context = context
 
     def __call__(self):
-        return self.factory()
+        return self.factory()  # pylint: disable=not-callable
 
 
 def register_factory(interface, klass, registry=None, name=''):
@@ -95,12 +97,13 @@
 
     :param interface: the interface for which the factory is registered
     :param klass: the object factory
-    :param registry: the registry into which factory adapter should be registered; if None, the global
-        registry is used
+    :param registry: the registry into which factory adapter should be registered; if None, the
+        global registry is used
     :param name: custom name given to registered factory
     """
 
     class Temp(ObjectFactoryAdapter):
+        """Object factory matching given interface"""
         classImplements(klass, interface)
         factory = klass
 
@@ -112,7 +115,7 @@
     registry.registerAdapter(Temp, name=if_name)
 
 
-class factory_config(object):
+class factory_config:  # pylint: disable=invalid-name,no-member
     """Class decorator to declare a default object factory"""
 
     venusian = venusian
@@ -125,23 +128,24 @@
         settings = self.__dict__.copy()
         depth = settings.pop('_depth', 0)
 
-        def callback(context, name, ob):
+        def callback(context, name, obj):
             name = settings.get('name', '')
             provided = settings.get('provided')
             if not provided:
-                raise TypeError("No provided interface(s) was given for registered factory %r" % ob)
+                raise TypeError("No provided interface(s) was given for registered factory %r" %
+                                obj)
             if not isinstance(provided, tuple):
                 provided = (provided,)
 
             config = context.config.with_package(info.module)
             for interface in provided:
                 if name:
-                    logger.debug("Registering factory {0} for interface {1} with name {2}".format(str(ob),
-                                                                                                  str(interface),
-                                                                                                  name))
+                    LOGGER.debug("Registering factory %s for interface %s with name %s",
+                                 str(obj), str(interface), name)
                 else:
-                    logger.debug("Registering default factory {0} for interface {1}".format(str(ob), str(interface)))
-                register_factory(interface, ob, config.registry, name)
+                    LOGGER.debug("Registering default factory %s for interface %s",
+                                 str(obj), str(interface))
+                register_factory(interface, obj, config.registry, name)
 
         info = self.venusian.attach(wrapped, callback, category='pyams_factory', depth=depth + 1)
         if info.scope == 'class':
--- a/src/pyams_utils/fanstatic.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/fanstatic.py	Sat Nov 23 00:38:44 2019 +0100
@@ -18,8 +18,6 @@
 template, or to get path of a given resources from a template.
 """
 
-__docformat__ = 'restructuredtext'
-
 from fanstatic import Resource
 from fanstatic.core import NeededResources, render_css, set_resource_file_existence_checking
 from pyramid.path import DottedNameResolver
@@ -29,6 +27,9 @@
 from pyams_utils.interfaces.tales import ITALESExtension
 
 
+__docformat__ = 'restructuredtext'
+
+
 def render_js(url, defer=False):
     """Render tag to include Javascript resource"""
     return '<script type="text/javascript" src="%s" %s></script>' % (url, 'defer' if defer else '')
@@ -59,10 +60,9 @@
         """Render resource tag"""
         if self.resource_type == 'css':
             return render_css(self.relpath)
-        elif self.resource_type == 'js':
+        if self.resource_type == 'js':
             return render_js(self.relpath, self.defer)
-        else:
-            return ''
+        return ''
 
 
 def get_resource_path(resource, signature='--static--', versioning=True):
--- a/src/pyams_utils/html.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/html.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,15 +10,17 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.html module
+
+This module provides functions which are used to convert HTML code to plain text, by extracting
+useful text and removing all HTML tags.
+"""
+
+from html.parser import HTMLParser
+from warnings import warn
 
 
-# import standard library
-from html.parser import HTMLParser
-
-# import interfaces
-
-# import packages
+__docformat__ = 'restructuredtext'
 
 
 class MyHTMLParser(HTMLParser):
@@ -27,22 +29,26 @@
     entitydefs = {'amp': '&', 'lt': '<', 'gt': '>',
                   'nbsp': ' ',
                   'apos': "'", 'quot': '"',
-                  'Agrave': 'À', 'Aacute': 'A', 'Acirc': 'Â', 'Atilde': 'A', 'Auml': 'Ä', 'Aring': 'A',
+                  'Agrave': 'À', 'Aacute': 'A', 'Acirc': 'Â', 'Atilde': 'A',
+                  'Auml': 'Ä', 'Aring': 'A',
                   'AElig': 'AE',
                   'Ccedil': 'Ç',
                   'Egrave': 'É', 'Eacute': 'È', 'Ecirc': 'Ê', 'Euml': 'Ë',
                   'Igrave': 'I', 'Iacute': 'I', 'Icirc': 'I', 'Iuml': 'I',
                   'Ntilde': 'N',
-                  'Ograve': 'O', 'Oacute': 'O', 'Ocirc': 'Ô', 'Otilde': 'O', 'Ouml': 'Ö', 'Oslash': 'O',
+                  'Ograve': 'O', 'Oacute': 'O', 'Ocirc': 'Ô', 'Otilde': 'O',
+                  'Ouml': 'Ö', 'Oslash': '0',
                   'Ugrave': 'Ù', 'Uacute': 'U', 'Ucirc': 'Û', 'Uuml': 'Ü',
                   'Yacute': 'Y',
                   'THORN': 'T',
-                  'agrave': 'à', 'aacute': 'a', 'acirc': 'â', 'atilde': 'a', 'auml': 'ä', 'aring': 'a', 'aelig': 'ae',
+                  'agrave': 'à', 'aacute': 'a', 'acirc': 'â', 'atilde': 'a',
+                  'auml': 'ä', 'aring': 'a', 'aelig': 'ae',
                   'ccedil': 'ç',
                   'egrave': 'è', 'eacute': 'é', 'ecirc': 'ê', 'euml': 'ë',
                   'igrave': 'i', 'iacute': 'i', 'icirc': 'î', 'iuml': 'ï',
                   'ntilde': 'n',
-                  'ograve': 'o', 'oacute': 'o', 'ocirc': 'ô', 'otilde': 'o', 'ouml': 'ö', 'oslash': 'o',
+                  'ograve': 'o', 'oacute': 'o', 'ocirc': 'ô', 'otilde': 'o',
+                  'ouml': 'ö', 'oslash': 'o',
                   'ugrave': 'ù', 'uacute': 'u', 'ucirc': 'û', 'uuml': 'ü',
                   'yacute': 'y',
                   'thorn': 't',
@@ -84,12 +90,12 @@
 
     def handle_charref(self, name):
         try:
-            n = int(name)
+            int_value = int(name)
         except ValueError:
             return
-        if not 0 <= n <= 255:
+        if not 0 <= int_value <= 255:
             return
-        self.handle_data(self.charrefs.get(n))
+        self.handle_data(self.charrefs.get(int_value))
 
     def handle_starttag(self, tag, attrs):
         if tag == 'td':
@@ -101,6 +107,9 @@
         if tag == 'p':
             self.data += '\n'
 
+    def error(self, message):
+        warn(message)
+
 
 def html_to_text(value):
     """Utility function to extract text content from HTML
@@ -120,7 +129,8 @@
     >>> html_to_text(html)
     'Header\\nThis is an < ò > entity.\\n\\n'
 
-    >>> html = '''<div><p>Header</p><p>This is an &lt;&nbsp;&#242;&nbsp;&gt; entity.<br /></p></div>'''
+    >>> html = '''<div><p>Header</p><p>This is an &lt;&nbsp;&#242;&nbsp;&gt; ''' + \
+               '''entity.<br /></p></div>'''
     >>> html_to_text(html)
     'Header\\nThis is an <\xa0ò\xa0> entity.\\n\\n'
     """
--- a/src/pyams_utils/i18n.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/i18n.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,25 +10,43 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-"""I18n module used to get browser language from request"""
+"""PyAMS_utils.i18n module
 
-__docformat__ = 'restructuredtext'
+This module is used to get browser language from request
+"""
 
 import locale
 
 
+__docformat__ = 'restructuredtext'
+
+
 def normalize_lang(lang):
-    """Normalize input languages string"""
-    lang = lang.strip().lower()
-    lang = lang.replace('_', '-')
-    lang = lang.replace(' ', '')
-    return lang
+    """Normalize input languages string
+
+    >>> from pyams_utils.i18n import normalize_lang
+    >>> lang = 'fr,en-US ; q=0.9, en-GB ; q=0.8, en ; q=0.7'
+    >>> normalize_lang(lang)
+    'fr,en-us;q=0.9,en-gb;q=0.8,en;q=0.7'
+    """
+    return lang.strip() \
+               .lower() \
+               .replace('_', '-') \
+               .replace(' ', '')
 
 
 def get_browser_language(request):
     """Custom locale negotiator
 
     Copied from zope.publisher code
+
+    >>> from pyramid.testing import DummyRequest
+    >>> from pyams_utils.i18n import get_browser_language
+
+    >>> request = DummyRequest()
+    >>> request.headers['Accept-Language'] = 'fr, en-US ; q=0.9, en-GB ; q=0.8, en ; q=0.7'
+    >>> get_browser_language(request)
+    'fr'
     """
     accept_langs = request.headers.get('Accept-Language', '').split(',')
 
@@ -75,7 +93,10 @@
 
 
 def set_locales(config):
-    """Define locale environment variables"""
+    """Define locale environment variables
+
+    :param config: Pyramid's settings object
+    """
     for attr in ('LC_CTYPE', 'LC_COLLATE', 'LC_TIME', 'LC_MONETARY', 'LC_NUMERIC', 'LC_ALL'):
         value = config.get('pyams.locale.{0}'.format(attr.lower()))
         if value:
--- a/src/pyams_utils/include.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/include.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,8 +10,6 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from chameleon import PageTemplateFile
 from persistent.interfaces import IPersistent
 from z3c.pt.pagetemplate import PageTemplateFile as Z3cPageTemplateFile
@@ -29,6 +27,8 @@
 from pyams_utils.url import get_display_context
 from pyams_utils.traversing import NamespaceTraverser
 
+__docformat__ = 'restructuredtext'
+
 
 def include_package(config):
     """Pyramid package include"""
--- a/src/pyams_utils/inherit.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/inherit.py	Sat Nov 23 00:38:44 2019 +0100
@@ -15,9 +15,114 @@
 This module is used to manage a generic inheritance between a content and
 it's parent container. It also defines a custom InheritedFieldProperty which
 allows to automatically manage inherited properties.
-"""
+
+This PyAMS module is used to handle inheritance between a parent object and a child which can
+"inherit" from some of it's properties, as long as they share the same "target" interface.
+
+    >>> from zope.interface import implementer, Interface, Attribute
+    >>> from zope.schema import TextLine
+    >>> from zope.schema.fieldproperty import FieldProperty
+
+    >>> from pyams_utils.adapter import adapter_config
+    >>> from pyams_utils.interfaces.inherit import IInheritInfo
+    >>> from pyams_utils.inherit import BaseInheritInfo, InheritedFieldProperty
+    >>> from pyams_utils.registry import get_global_registry
+
+Let's start by creating a "content" interface, and a marker interface for objects for which we
+want to provide this interface:
+
+    >>> class IMyInfoInterface(IInheritInfo):
+    ...     '''Custom interface'''
+    ...     value = TextLine(title="Custom attribute")
+
+    >>> class IMyTargetInterface(Interface):
+    ...     '''Target interface'''
+
+    >>> @implementer(IMyInfoInterface)
+    ... class MyInfo(BaseInheritInfo):
+    ...     target_interface = IMyTargetInterface
+    ...     adapted_interface = IMyInfoInterface
+    ...
+    ...     _value = FieldProperty(IMyInfoInterface['value'])
+    ...     value = InheritedFieldProperty(IMyInfoInterface['value'])
+
+Please note that for each field of the interface which can be inherited, you must define to
+properties: one using "InheritedFieldProperty" with the name of the field, and one using a classic
+"FieldProperty" with the same name prefixed by "_"; this property is used to store the "local"
+property value, when inheritance is unset.
+
+The adapter is created to adapt an object providing IMyTargetInterface to IMyInfoInterface;
+please note that the adapter *must* attach the created object to it's parent by setting
+__parent__ attribute:
+
+    >>> @adapter_config(context=IMyTargetInterface, provides=IMyInfoInterface)
+    ... def my_info_factory(context):
+    ...     info = getattr(context, '__info__', None)
+    ...     if info is None:
+    ...         info = context.__info__ = MyInfo()
+    ...         info.__parent__ = context
+    ...     return info
+
+Adapter registration is here only for testing; the "adapter_config" decorator may do the job in
+a normal application context:
+
+    >>> registry = get_global_registry()
+    >>> registry.registerAdapter(my_info_factory, (IMyTargetInterface, ), IMyInfoInterface)
 
-__docformat__ = 'restructuredtext'
+We can then create classes which will be adapted to support inheritance:
+
+    >>> @implementer(IMyTargetInterface)
+    ... class MyTarget:
+    ...     '''Target class'''
+    ...     __parent__ = None
+    ...     __info__ = None
+
+    >>> parent = MyTarget()
+    >>> parent_info = IMyInfoInterface(parent)
+    >>> parent.__info__
+    <pyams_utils.tests.test_utils...MyInfo object at ...>
+    >>> parent_info.value = 'parent'
+    >>> parent_info.value
+    'parent'
+    >>> parent_info.can_inherit
+    False
+
+As soon as a parent is defined, the child object can inherit from it's parent:
+
+    >>> child = MyTarget()
+    >>> child.__parent__ = parent
+    >>> child_info = IMyInfoInterface(child)
+    >>> child.__info__
+    <pyams_utils.tests.test_utils...MyInfo object at ...>
+
+    >>> child_info.can_inherit
+    True
+    >>> child_info.inherit
+    True
+    >>> child_info.value
+    'parent'
+
+Setting child value while inheritance is enabled donesn't have any effect:
+
+    >>> child_info.value = 'child'
+    >>> child_info.value
+    'parent'
+    >>> child_info.inherit_from == parent
+    True
+
+You can disable inheritance and define your own value:
+
+    >>> child_info.inherit = False
+    >>> child_info.value = 'child'
+    >>> child_info.value
+    'child'
+    >>> child_info.inherit_from == child
+    True
+
+Please note that parent and child in this example share the same class, but this is not a
+requirement; they just have to implement the same marker interface, to be adapted to the same
+content interface.
+"""
 
 from zope.interface import Interface, implementer
 from zope.location import Location
@@ -28,9 +133,17 @@
 from pyams_utils.zodb import volatile_property
 
 
+__docformat__ = 'restructuredtext'
+
+
 @implementer(IInheritInfo)
 class BaseInheritInfo(Location):
-    """Base inherit class"""
+    """Base inherit class
+
+    Subclasses may generaly override target_interface and adapted_interface to
+    correctly handle inheritance (see example in doctests).
+    Please note also that adapters to this interface must correctly 'locate'
+    """
 
     target_interface = Interface
     adapted_interface = Interface
@@ -73,14 +186,14 @@
     def inherit_from(self):
         """Get current parent from which we inherit"""
         if not self.inherit:
-            return self
+            return self.__parent__
         parent = self.parent
         while self.adapted_interface(parent).inherit:
-            parent = parent.parent
+            parent = parent.parent  # pylint: disable=no-member
         return parent
 
 
-class InheritedFieldProperty(object):
+class InheritedFieldProperty:
     """Inherited field property"""
 
     def __init__(self, field, name=None):
@@ -94,10 +207,10 @@
         if inst is None:
             return self
         inherit_info = IInheritInfo(inst)
-        if inherit_info.inherit:
+        if inherit_info.inherit and (inherit_info.parent is not None):
+            # pylint: disable=not-callable
             return getattr(inherit_info.adapted_interface(inherit_info.parent), self.__name)
-        else:
-            return getattr(inst, '_{0}'.format(self.__name))
+        return getattr(inst, '_{0}'.format(self.__name))
 
     def __set__(self, inst, value):
         inherit_info = IInheritInfo(inst)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/pygments.py	Sat Nov 23 00:38:44 2019 +0100
@@ -0,0 +1,55 @@
+#
+# Copyright (c) 2008-2018 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_utils.interfaces.pygments module
+
+This module is used to load Pygments CSS
+"""
+
+from zope.interface import Interface
+from zope.schema import Bool, Choice
+
+
+__docformat__ = 'restructuredtext'
+
+from pyams_utils import _
+
+
+PYGMENTS_LEXERS_VOCABULARY = 'Pygments lexers vocabulary'
+PYGMENTS_STYLES_VOCABULARY = 'Pygments styles vocabulary'
+
+
+class IPygmentsCodeConfiguration(Interface):
+    """Pygments html formatter options"""
+
+    lexer = Choice(title=_("Selected lexer"),
+                   description=_("Lexer used to format source code"),
+                   required=True,
+                   vocabulary=PYGMENTS_LEXERS_VOCABULARY,
+                   default='auto')
+
+    display_linenos = Bool(title=_("Display line numbers?"),
+                           description=_("If 'no', line numbers will be hidden"),
+                           required=True,
+                           default=True)
+
+    disable_wrap = Bool(title=_("Lines wrap?"),
+                        description=_("If 'yes', lines wraps will be enabled; line numbers will "
+                                      "not be displayed if lines wrap is enabled..."),
+                        required=True,
+                        default=False)
+
+    style = Choice(title=_("Color style"),
+                   description=_("Selected color style"),
+                   required=True,
+                   vocabulary=PYGMENTS_STYLES_VOCABULARY,
+                   default='default')
--- a/src/pyams_utils/interfaces/site.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/interfaces/site.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,17 +10,12 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.interface import Attribute, Interface
+from zope.interface.interfaces import IObjectEvent
 
 
-# import standard library
-
-# import interfaces
-from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.component.interfaces import IObjectEvent
-
-# import packages
-from zope.interface import Interface, Attribute
+__docformat__ = 'restructuredtext'
 
 
 class ISiteRoot(IAttributeAnnotatable):
--- a/src/pyams_utils/intids.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/intids.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,26 +10,29 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_utils.intids module
 
-# import standard packages
+This module provides utility functions and helpers to help usage of IIntIds utilities.
+Pyramid events subscribers are also declared to match Zope events with Pyramid IntIds related
+events
+"""
 
-# import interfaces
 from persistent.interfaces import IPersistent
-from pyams_utils.interfaces.intids import IUniqueID
-from zope.intid.interfaces import IIntIds, IIntIdEvent, IntIdAddedEvent, IntIdRemovedEvent
-from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
-from zope.location.interfaces import ISublocations
-from zope.keyreference.interfaces import IKeyReference, NotYet
-
-# import packages
-from pyams_utils.adapter import adapter_config, ContextAdapter
-from pyams_utils.registry import get_all_utilities_registered_for, query_utility
 from pyramid.events import subscriber
 from pyramid.threadlocal import get_current_registry
 from zope.intid import intIdEventNotify
+from zope.intid.interfaces import IIntIdEvent, IIntIds, IntIdAddedEvent, IntIdRemovedEvent
+from zope.keyreference.interfaces import IKeyReference, NotYet
 from zope.lifecycleevent import ObjectRemovedEvent
+from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+from zope.location.interfaces import ISublocations
+
+from pyams_utils.adapter import ContextAdapter, adapter_config
+from pyams_utils.interfaces.intids import IUniqueID
+from pyams_utils.registry import get_all_utilities_registered_for, query_utility
+
+
+__docformat__ = 'restructuredtext'
 
 
 @adapter_config(context=IPersistent, provides=IUniqueID)
@@ -46,6 +49,7 @@
         intids = query_utility(IIntIds)
         if intids is not None:
             return hex(intids.queryId(self.context))[2:]
+        return None
 
 
 @subscriber(IObjectAddedEvent, context_selector=IPersistent)
@@ -82,7 +86,7 @@
     registry = get_current_registry()
     locations = ISublocations(event.object, None)
     if locations is not None:
-        for location in locations.sublocations():
+        for location in locations.sublocations():  # pylint: disable=too-many-function-args
             registry.notify(ObjectRemovedEvent(location))
     utilities = tuple(get_all_utilities_registered_for(IIntIds))
     if utilities:
@@ -100,7 +104,9 @@
 
 @subscriber(IIntIdEvent)
 def handle_intid_event(event):
-    """Event subscriber used to dispatch all IIntIdEvent events using Pyramid events subscribers to matching
-    subscribers using Zope events
+    """IntId event subscriber
+
+    This event is used to dispatch all IIntIdEvent events using Pyramid events subscribers
+    to matching subscribers using Zope events
     """
     intIdEventNotify(event)
--- a/src/pyams_utils/json.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/json.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,26 +10,36 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.json package
+
+A small utility module which provides a default JSON encoder to automatically convert
+dates and datetimes to ISO format
 
+    >>> import json as stock_json
+    >>> from datetime import datetime
+    >>> from pyams_utils import json
+    >>> from pyams_utils.timezone import GMT
 
-# import standard library
+    >>> value = datetime.fromtimestamp(1205000000, GMT)
+    >>> stock_json.dumps(value)
+    '"2008-03-08T18:13:20+00:00"'
+"""
+
 import json
 
 from datetime import date, datetime
 
-# import interfaces
-
-# import packages
+__docformat__ = 'restructuredtext'
 
 
 def default_json_encoder(obj):
+    """Default JSON encoding of dates and datetimes"""
     if isinstance(obj, (date, datetime)):
         return obj.isoformat()
-    else:
-        return obj
+    return obj
 
 
+# pylint: disable=protected-access
 json._default_encoder = json.JSONEncoder(skipkeys=False,
                                          ensure_ascii=True,
                                          check_circular=True,
--- a/src/pyams_utils/list.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/list.py	Sat Nov 23 00:38:44 2019 +0100
@@ -20,8 +20,6 @@
 iterator).
 """
 
-__docformat__ = 'restructuredtext'
-
 from itertools import filterfalse, tee
 from random import random, shuffle
 
@@ -31,14 +29,17 @@
 from pyams_utils.interfaces.tales import ITALESExtension
 
 
+__docformat__ = 'restructuredtext'
+
+
 def unique(seq, key=None):
     """Extract unique values from list, preserving order
 
     :param iterator seq: input list
     :param callable key: an identity function which is used to get 'identity' value of each element
         in the list
-    :return: list; a new list containing only unique elements of the original list in their initial order.
-        Original list is not modified.
+    :return: list; a new list containing only unique elements of the original list in their initial
+        order. Original list is not modified.
 
     >>> from pyams_utils.list import unique
     >>> mylist = [1, 2, 3, 2, 1]
@@ -173,7 +174,8 @@
     return next(values), values
 
 
-@adapter_config(name='boolean_iter', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='boolean_iter', context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
 class IterValuesCheckerExpression(ContextRequestViewAdapter):
     """TALES expression used to handle iterators
 
@@ -182,6 +184,7 @@
     """
 
     def render(self, context=None):
+        """Render TALES extension; see `ITALESExtension` interface"""
         if context is None:
             context = self.context
         return boolean_iter(context)
--- a/src/pyams_utils/lock.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/lock.py	Sat Nov 23 00:38:44 2019 +0100
@@ -16,24 +16,25 @@
 processes; the lock relies on a shared value stored info Beaker's cache.
 """
 
-__docformat__ = 'restructuredtext'
-
 import time
 from threading import local
 
 from beaker import cache
 
 
-_local = local()
+__docformat__ = 'restructuredtext'
+
+
+_LOCAL = local()
 
 
 def get_locks_cache():
     """Get locks shared cache"""
     try:
-        locks_cache = _local.locks_cache
+        locks_cache = _LOCAL.locks_cache
     except AttributeError:
         manager = cache.CacheManager(**cache.cache_regions['persistent'])
-        locks_cache = _local.locks_cache = manager.get_cache('PyAMS::locks')
+        locks_cache = _LOCAL.locks_cache = manager.get_cache('PyAMS::locks')
     return locks_cache
 
 
@@ -41,7 +42,7 @@
     """Cache lock exception"""
 
 
-class CacheLock(object):
+class CacheLock:
     """Beaker based lock
 
     This lock can be used when you need to get a lot across several processes or even computers.
@@ -66,8 +67,7 @@
             if test:
                 if not self.wait:
                     raise LockException()
-                else:
-                    time.sleep(0.1)
+                time.sleep(0.1)
             else:
                 locks_cache.set_value(self.key, 1)
                 self.has_lock = True
--- a/src/pyams_utils/progress.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/progress.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,7 +10,28 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.progress module
+
+This module can be used to get progress status on a long running operation.
+
+The process is a s follow:
+
+ - the client generate a "progress ID"; this ID can be any unique ID, and can be generated by
+   MyAMS client library
+ - the client browser send a POST request containg this progress ID to a view
+ - the view calls "init_progress_status(progress_id, request.principal.id, "Task label") when
+   starting it's long operation
+ - during the operation, a call is made regularly to "set_progress_status(progress_id)"; additional
+   arguments can contain a status (to indicate if operation is finished or not), and a simple
+   "message" or two "length" and "current" arguments which can specify the length of the operation
+   and it's current position
+ - at the end of the operation, the view calls "set_progress_status(progress_id, 'finished')" to
+   specify that the operation is finished.
+
+During the whole operation, while waiting for server response, the client browser can send
+requests to "get_progress_status.json", providing the progress ID, to get the operation progress.
+This last operation is done automatically in PyAMS forms.
+"""
 
 from datetime import datetime
 from threading import local
@@ -22,7 +43,10 @@
 from pyams_utils.lock import locked
 
 
-_local = local()
+__docformat__ = 'restructuredtext'
+
+
+_LOCAL = local()
 
 
 PROGRESS_CACHE_NAME = 'PyAMS::progress'
@@ -34,20 +58,20 @@
 def get_tasks_cache():
     """Get cache storing tasks list"""
     try:
-        tasks_cache = _local.running_tasks_cache
+        tasks_cache = _LOCAL.running_tasks_cache
     except AttributeError:
         manager = cache.CacheManager(**cache.cache_regions['persistent'])
-        tasks_cache = _local.running_tasks_cache = manager.get_cache(PROGRESS_CACHE_NAME)
+        tasks_cache = _LOCAL.running_tasks_cache = manager.get_cache(PROGRESS_CACHE_NAME)
     return tasks_cache
 
 
 def get_progress_cache():
     """Get cache storing tasks progress"""
     try:
-        local_cache = _local.progress_cache
+        local_cache = _LOCAL.progress_cache
     except AttributeError:
         manager = cache.CacheManager(**cache.cache_regions['default'])
-        local_cache = _local.progress_cache = manager.get_cache(PROGRESS_CACHE_NAME)
+        local_cache = _LOCAL.progress_cache = manager.get_cache(PROGRESS_CACHE_NAME)
     return local_cache
 
 
@@ -65,6 +89,7 @@
 
 @locked(name=PROGRESS_LOCK_NAME)
 def init_progress_status(progress_id, owner, label, tags=None, length=None, current=None):
+    # pylint: disable=too-many-arguments
     """Initialize progress status for given task
 
     :param str progress_id: task ID
--- a/src/pyams_utils/property.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/property.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,38 +10,57 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.property module
+
+This module is used to define:
+ - a cached property; this read-only property is evaluated only once; it's value is stored into
+   object's attributes, and so should be freed with the object (so it should behave like a
+   Pyramid's "reify" decorator, but we have kept it for compatibility of existing code)
+ - a class property; this decorator is working like a classic property, but can be assigned to a
+   class; to support class properties, this class also have to decorated with the
+   "classproperty_support" decorator
+
+    >>> from pyams_utils.property import cached_property
+
+    >>> class ClassWithCache:
+    ...     '''Class with cache'''
+    ...     @cached_property
+    ...     def cached_value(self):
+    ...         print("This is a cached value")
+    ...         return 1
+
+    >>> obj = ClassWithCache()
+    >>> obj.cached_value
+    This is a cached value
+    1
+
+On following calls, cached property method shouldn't be called again:
+
+    >>> obj.cached_value
+    1
+
+Class properties are used to define properties on class level:
+
+    >>> from pyams_utils.property import classproperty, classproperty_support
+
+    >>> @classproperty_support
+    ... class ClassWithProperties:
+    ...     '''Class with class properties'''
+    ...
+    ...     class_attribute = 1
+    ...
+    ...     @classproperty
+    ...     def my_class_property(cls):
+    ...         return cls.class_attribute
+
+    >>> ClassWithProperties.my_class_property
+    1
+"""
+
 __docformat__ = 'restructuredtext'
 
 
-# import standard library
-
-# import interfaces
-
-# import packages
-
-
-class cached(object):
-    """Custom property decorator to define a property or function which is calculated only once
-
-    When applied on a function, caching is based on input arguments
-    """
-
-    def __init__(self, function):
-        self._function = function
-        self._cache = {}
-
-    def __call__(self, *args):
-        try:
-            return self._cache[args]
-        except KeyError:
-            self._cache[args] = self._function(*args)
-            return self._cache[args]
-
-    def expire(self, *args):
-        del self._cache[args]
-
-
-class cached_property(object):
+class cached_property:  # pylint: disable=invalid-name
     """A read-only property decorator that is only evaluated once.
 
     The value is cached on the object itself rather than the function or class; this should prevent
@@ -60,9 +79,9 @@
         return result
 
 
-class classproperty:
+class classproperty:  # pylint: disable=invalid-name
     """Same decorator as property(), but passes obj.__class__ instead of obj to fget/fset/fdel.
-    
+
     Original code for property emulation:
     https://docs.python.org/3.5/howto/descriptor.html#properties
     """
@@ -92,27 +111,30 @@
         self.fdel(obj.__class__)
 
     def getter(self, fget):
+        """Property getter"""
         return type(self)(fget, self.fset, self.fdel, self.__doc__)
 
     def setter(self, fset):
+        """Property setter"""
         return type(self)(self.fget, fset, self.fdel, self.__doc__)
 
     def deleter(self, fdel):
+        """Property deleter"""
         return type(self)(self.fget, self.fset, fdel, self.__doc__)
 
 
 def classproperty_support(cls):
     """Class decorator to add metaclass to a class.
-    
+
     Metaclass uses to add descriptors to class attributes
     """
     class Meta(type):
-        pass
+        """Meta class"""
 
     for name, obj in vars(cls).items():
         if isinstance(obj, classproperty):
             setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel))
 
     class Wrapper(cls, metaclass=Meta):
-        pass
+        """Wrapper class"""
     return Wrapper
--- a/src/pyams_utils/pygments.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/pygments.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.pygments module
+
+This module is used to provide an URL which allows you to load Pygments CSS files.
+It also provides two vocabularies of available lexers and styles.
+"""
 
 from fanstatic import get_library_registry
 from persistent import Persistent
@@ -21,16 +25,19 @@
 from pyramid.response import Response
 from pyramid.view import view_config
 from zope.container.contained import Contained
-from zope.interface import Interface, implementer
-from zope.schema import Bool, Choice
 from zope.schema.fieldproperty import FieldProperty
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
 from pyams_utils.factory import factory_config
 from pyams_utils.fanstatic import ExternalResource
+from pyams_utils.interfaces.pygments import IPygmentsCodeConfiguration, \
+    PYGMENTS_LEXERS_VOCABULARY, PYGMENTS_STYLES_VOCABULARY
 from pyams_utils.list import unique_iter
 from pyams_utils.vocabulary import vocabulary_config
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
@@ -47,11 +54,13 @@
         from pyams_default_theme import library
 
 
+# pylint: disable=invalid-name
 pygments_css = ExternalResource(library, 'get-pygments-style.css', resource_type='css')
 
 
 @view_config(name='get-pygments-style.css')
 def get_pygments_style_view(request):
+    """View used to download Pygments style"""
     style = request.params.get('style', 'default')
     styles = HtmlFormatter(linenos='inline',
                            nowrap=False,
@@ -64,32 +73,28 @@
 # Pygments lexers
 #
 
-PYGMENTS_LEXERS_VOCABULARY = 'Pygments lexers vocabulary'
-
-
 @vocabulary_config(name=PYGMENTS_LEXERS_VOCABULARY)
 class PygmentsLexersVocabulary(SimpleVocabulary):
     """Pygments lexers vocabulary"""
 
-    def __init__(self, context):
+    def __init__(self, context):  # pylint: disable=unused-argument
         terms = [SimpleTerm('auto', title=_("Automatic detection"))]
+        # pylint: disable=unused-variable
         for name, aliases, filetypes, mimetypes in sorted(unique_iter(get_all_lexers(),
                                                                       key=lambda x: x[0].lower()),
                                                           key=lambda x: x[0].lower()):
             terms.append(SimpleTerm(aliases[0] if len(aliases) > 0 else name,
                                     title='{0}{1}'.format(name,
-                                                          ' ({})'.format(', '.join(filetypes)) if filetypes else '')))
+                                                          ' ({})'.format(', '.join(filetypes))
+                                                          if filetypes else '')))
         super(PygmentsLexersVocabulary, self).__init__(terms)
 
 
-PYGMENTS_STYLES_VOCABULARY = 'Pygments styles vocabulary'
-
-
 @vocabulary_config(name=PYGMENTS_STYLES_VOCABULARY)
 class PygmentsStylesVocabulary(SimpleVocabulary):
     """Pygments styles vocabulary"""
 
-    def __init__(self, context):
+    def __init__(self, context):  # pylint: disable=unused-argument
         terms = []
         for name in sorted(get_all_styles()):
             terms.append(SimpleTerm(name))
@@ -100,33 +105,6 @@
 # Pygments configuration
 #
 
-class IPygmentsCodeConfiguration(Interface):
-    """Pygments html formatter options"""
-
-    lexer = Choice(title=_("Selected lexer"),
-                   description=_("Lexer used to format source code"),
-                   required=True,
-                   vocabulary=PYGMENTS_LEXERS_VOCABULARY,
-                   default='auto')
-
-    display_linenos = Bool(title=_("Display line numbers?"),
-                           description=_("If 'no', line numbers will be hidden"),
-                           required=True,
-                           default=True)
-
-    disable_wrap = Bool(title=_("Lines wrap?"),
-                        description=_("If 'yes', lines wraps will be enabled; line numbers will not be "
-                                      "displayed if lines wrap is enabled..."),
-                        required=True,
-                        default=False)
-
-    style = Choice(title=_("Color style"),
-                   description=_("Selected color style"),
-                   required=True,
-                   vocabulary=PYGMENTS_STYLES_VOCABULARY,
-                   default='default')
-
-
 @factory_config(IPygmentsCodeConfiguration)
 class PygmentsCodeRendererSettings(Persistent, Contained):
     """Pygments code renderer settings"""
@@ -149,3 +127,4 @@
                                   cssclass='source',
                                   style=settings.style)
         return highlight(code, lexer, formatter)
+    return None
--- a/src/pyams_utils/registry.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/registry.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,13 +10,13 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__doc__ = """Registry management package
+"""PyAMS_utils.registry module
 
 This package is used to manage *local registry*. A local registry is a *site management* component
-created automatically on application startup by PyAMS_utils package. It can be used to store and register
-components, mainly utilities which are created and configured dynamically by a site administrator; this can include
-SQLAlchemy engines, ZEO connections, and several PyAMS utilities like security manager, medias converter,
-tasks scheduler and many other ones.
+created automatically on application startup by PyAMS_utils package. It can be used to store and
+register components, mainly utilities which are created and configured dynamically by a site
+administrator; this can include SQLAlchemy engines, ZEO connections, and several PyAMS utilities
+like security manager, medias converter, tasks scheduler and many other ones.
 
 See :ref:`zca` to get a brief introduction about using a local registry with PyAMS packages.
 """
@@ -39,7 +39,7 @@
 
 __docformat__ = 'restructuredtext'
 
-logger = logging.getLogger('PyAMS (utils)')
+LOGGER = logging.getLogger('PyAMS (utils)')
 
 
 class LocalRegistry(threading.local):
@@ -196,7 +196,7 @@
     return result
 
 
-class utility_config(object):
+class utility_config(object):  # 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
@@ -237,23 +237,24 @@
                 else:
                     raise TypeError("Missing 'provides' argument")
 
-            config = context.config.with_package(info.module)
-            logger.debug("Registering utility {0} named '{1}' providing {2}".format(
+            config = context.config.with_package(info.module)  # pylint: disable=no-member
+            LOGGER.debug("Registering utility {0} named '{1}' providing {2}".format(
                 str(component) if component else str(factory),
                 settings.get('name', ''),
                 str(provides)))
-            config.registry.registerUtility(component=component, factory=factory,
-                                            provided=provides, name=settings.get('name', ''))
+            registry = settings.get('registry', config.registry)
+            registry.registerUtility(component=component, factory=factory,
+                                     provided=provides, name=settings.get('name', ''))
 
         info = self.venusian.attach(wrapped, callback, category='pyams_utility',
                                     depth=depth + 1)
 
-        if info.scope == 'class':
+        if info.scope == 'class':  # pylint: disable=no-member
             # if the decorator was attached to a method in a class, or
             # otherwise executed at class scope, we need to set an
             # 'attr' into the settings if one isn't already in there
             if settings.get('attr') is None:
                 settings['attr'] = wrapped.__name__
 
-        settings['_info'] = info.codeinfo  # fbo "action_method"
+        settings['_info'] = info.codeinfo  # pylint: disable=no-member
         return wrapped
--- a/src/pyams_utils/request.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/request.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,7 +10,10 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.request module
+
+
+"""
 
 import logging
 
@@ -24,13 +27,15 @@
 from pyams_utils.interfaces import ICacheKeyValue, MissingRequestError
 from pyams_utils.registry import get_global_registry
 
-
-logger = logging.getLogger('PyAMS (utils)')
-
-_marker = object()
+__docformat__ = 'restructuredtext'
 
 
-class RequestSelector(object):
+LOGGER = logging.getLogger('PyAMS (utils)')
+
+_MARKER = object()
+
+
+class RequestSelector:
     """Interface based request selector
 
     This selector can be used as a subscriber predicate to define
@@ -45,12 +50,13 @@
             '''This is an event handler for an IPyAMSRequest modification event'''
     """
 
-    def __init__(self, ifaces, config):
+    def __init__(self, ifaces, config):  # pylint: disable=unused-argument
         if not isinstance(ifaces, (list, tuple, set)):
             ifaces = (ifaces,)
         self.interfaces = ifaces
 
     def text(self):
+        """Predicate label"""
         return 'request_selector = %s' % str(self.interfaces)
 
     phash = text
@@ -72,9 +78,10 @@
     If no request is currently running, a new one is created.
     `key` is a required argument; if None, the key will be the method's object
 
-    :param str key: annotations value key; if *None*, the key will be the method's object; if *key* is a callable
-        object, it will be called to get the actual session key
-    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property name
+    :param str key: annotations value key; if *None*, the key will be the method's object; if
+        *key* is a callable object, it will be called to get the actual session key
+    :param prefix: str; prefix to use for session key; if *None*, the prefix will be the property
+        name
     """
 
     def request_decorator(func):
@@ -93,18 +100,19 @@
                         key += '::' + '::'.join((ICacheKeyValue(arg) for arg in key_args))
                     if kwargs:
                         key += '::' + \
-                               '::'.join(('{0}={1}'.format(key, ICacheKeyValue(val)) for key, val in kwargs.items()))
-                logger.debug(">>> Looking for request cache key {0}".format(key))
-                data = get_request_data(request, key, _marker)
-                if data is _marker:
-                    logger.debug("<<< no cached value!")
+                               '::'.join(('{0}={1}'.format(key, ICacheKeyValue(val))
+                                          for key, val in kwargs.items()))
+                LOGGER.debug(">>> Looking for request cache key {0}".format(key))
+                data = get_request_data(request, key, _MARKER)
+                if data is _MARKER:
+                    LOGGER.debug("<<< no cached value!")
                     data = func
                     if callable(data):
                         data = data(obj, *args, **kwargs)
                     set_request_data(request, key, data)
                 else:
-                    logger.debug("<<< cached value found!")
-                    logger.debug("  < {0!r}".format(data))
+                    LOGGER.debug("<<< cached value found!")
+                    LOGGER.debug("  < {0!r}".format(data))
             else:
                 data = func
                 if callable(data):
@@ -126,7 +134,7 @@
     @request_property(key=None)
     def has_permission(self, permission, context=None):
         if context is None:
-            context = self.context
+            context = self.context  # pylint: disable=no-member
         try:
             reg = self.registry
         except AttributeError:
@@ -163,6 +171,7 @@
         return None
 
 
+# pylint: disable=invalid-name,too-many-arguments
 def check_request(path='/', environ=None, base_url=None, headers=None,
                   POST=None, registry=None, principal_id=None, **kwargs):
     """Get current request, or create a new blank one if missing"""
@@ -177,13 +186,15 @@
         if factory is None:
             factory = PyAMSRequest
         request = factory.blank(path, environ, base_url, headers, POST, **kwargs)
-        request.registry = registry
+        request.registry = registry  # pylint: disable=attribute-defined-outside-init
         if principal_id is not None:
             try:
+                # pylint: disable=import-outside-toplevel
                 from pyams_security.utility import get_principal
             except ImportError:
                 pass
             else:
+                # pylint: disable=attribute-defined-outside-init
                 request.principal = get_principal(request, principal_id)
         return request
 
@@ -210,12 +221,13 @@
     return IAnnotations(request)
 
 
-def get_debug(request):
+def get_debug(request):  # pylint: disable=unused-argument
     """Define 'debug' request property
 
     This function is automatically defined as a custom request method on package include.
     """
     class Debug():
+        """Request debug class"""
         def __init__(self):
             self.showTAL = False
             self.sourceAnnotations = False
@@ -232,7 +244,7 @@
     """
     try:
         annotations = request.annotations
-    except (TypeError, AttributeError) as e:
+    except (TypeError, AttributeError):
         annotations = get_annotations(request)
     return annotations.get(key, default)
 
@@ -246,6 +258,6 @@
     """
     try:
         annotations = request.annotations
-    except (TypeError, AttributeError) as e:
+    except (TypeError, AttributeError):
         annotations = get_annotations(request)
     annotations[key] = value
--- a/src/pyams_utils/schema.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/schema.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,6 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+"""PyAMS_utils.schema module
+
+This module is used to define custom schema fields
+"""
+
 import re
 import string
 
@@ -69,10 +74,10 @@
 
     _type = None
 
-    def fromUnicode(self, str):
+    def fromUnicode(self, str):  # pylint: disable=redefined-builtin
         return str
 
-    def constraint(self, value):
+    def constraint(self, value):  # pylint: disable=method-hidden
         return True
 
 
@@ -135,8 +140,8 @@
     def _validate(self, value):
         if len(value) not in (3, 6):
             raise ValidationError(_("Color length must be 3 or 6 characters"))
-        for v in value:
-            if v not in string.hexdigits:
+        for val in value:
+            if val not in string.hexdigits:
                 raise ValidationError(_("Color value must contain only valid hexadecimal color "
                                         "codes (numbers or letters between 'A' end 'F')"))
         super(ColorField, self)._validate(value)
@@ -196,10 +201,12 @@
     """Marker interface for mail address field"""
 
 
-EMAIL_REGEX = re.compile("^[^ @]+@[^ @]+\.[^ @]+$")
+EMAIL_REGEX = re.compile(r"^[^ @]+@[^ @]+\.[^ @]+$")
 
 
 class InvalidEmail(ValidationError):
+    """Invalid email validation error"""
+
     __doc__ = _(
         "Email address must be entered as « name@domain.name », without '<' and '>' characters")
 
--- a/src/pyams_utils/session.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/session.py	Sat Nov 23 00:38:44 2019 +0100
@@ -18,9 +18,9 @@
 It also adds to function to get and set session data.
 """
 
-__docformat__ = 'restructuredtext'
+from pyams_utils.request import check_request
 
-from pyams_utils.request import check_request
+__docformat__ = 'restructuredtext'
 
 
 def get_session_data(request, app, key, default=None):
@@ -66,7 +66,7 @@
     session['{0}::{1}'.format(app, key)] = value
 
 
-_marker = object()
+_MARKER = object()
 
 
 def session_property(app, key=None, prefix=None):
@@ -89,8 +89,8 @@
                 key = key(obj, *args, **kwargs)
             if not key:
                 key = '{1}::{0!r}'.format(obj, prefix or func.__name__)
-            data = get_session_data(request, app, key, _marker)
-            if data is _marker:
+            data = get_session_data(request, app, key, _MARKER)
+            if data is _MARKER:
                 data = func
                 if callable(data):
                     data = data(obj, *args, **kwargs)
--- a/src/pyams_utils/site.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/site.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,33 +10,33 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+"""PyAMS_utils.site module
+"""
 
-# import interfaces
-from pyams_utils.interfaces import PYAMS_APPLICATION_SETTINGS_KEY, PYAMS_APPLICATION_DEFAULT_NAME, \
-    PYAMS_APPLICATION_FACTORY_KEY, PUBLIC_PERMISSION
-from pyams_utils.interfaces.site import ISiteRoot, ISiteRootFactory, INewLocalSiteCreatedEvent, ISiteUpgradeEvent, \
-    ISiteGenerations, SITE_GENERATIONS_KEY, IConfigurationManager
-from zope.component.interfaces import IPossibleSite, ObjectEvent
+from persistent.dict import PersistentDict
+from pyramid.exceptions import NotFound
+from pyramid.path import DottedNameResolver
+from pyramid.security import ALL_PERMISSIONS, Allow, Everyone
+from pyramid.threadlocal import get_current_registry
+from pyramid_zodbconn import get_connection
+from zope.component import hooks
+from zope.component.interfaces import IPossibleSite
+from zope.container.folder import Folder
+from zope.interface import implementer
+from zope.interface.interfaces import ObjectEvent
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.site.site import LocalSiteManager, SiteManagerContainer
 from zope.traversing.interfaces import ITraversable
 
-# import packages
-from persistent.dict import PersistentDict
-from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
+from pyams_utils.interfaces import PUBLIC_PERMISSION, PYAMS_APPLICATION_DEFAULT_NAME, \
+    PYAMS_APPLICATION_FACTORY_KEY, PYAMS_APPLICATION_SETTINGS_KEY
+from pyams_utils.interfaces.site import IConfigurationManager, INewLocalSiteCreatedEvent, \
+    ISiteGenerations, ISiteRoot, ISiteRootFactory, ISiteUpgradeEvent, SITE_GENERATIONS_KEY
 from pyams_utils.registry import get_utilities_for, query_utility
-from pyramid.exceptions import NotFound
-from pyramid.path import DottedNameResolver
-from pyramid.security import Allow, Everyone, ALL_PERMISSIONS
-from pyramid.threadlocal import get_current_registry
-from pyramid_zodbconn import get_connection
-from zope.container.folder import Folder
-from zope.interface import implementer
-from zope.lifecycleevent import ObjectCreatedEvent
-from zope.site import hooks
-from zope.site.site import LocalSiteManager, SiteManagerContainer
+
+
+__docformat__ = 'restructuredtext'
 
 
 @implementer(ISiteRoot, IConfigurationManager)
@@ -139,7 +139,8 @@
                 if not current:
                     print("Upgrading {0} to generation {1}...".format(name, utility.generation))
                 elif current < utility.generation:
-                    print("Upgrading {0} from generation {1} to {2}...".format(name, current, utility.generation))
+                    print("Upgrading {0} from generation {1} to {2}...".format(name, current,
+                                                                               utility.generation))
                 utility.evolve(application, current)
                 generations[name] = utility.generation
         finally:
@@ -152,14 +153,16 @@
 def check_required_utilities(site, utilities):
     """Utility function to check for required utilities
 
-    :param object site: the site manager into which configuration may be checked
-    :param tuple utilities: each element of the tuple is another tuple made of the utility interface,
-        the utility registration name, the utility factory and the object name when creating the utility, as in:
+    :param ISite site: the site manager into which configuration may be checked
+    :param tuple utilities: each element of the tuple is another tuple made of the utility
+        interface, the utility registration name, the utility factory and the object name when
+        creating the utility, as in:
 
     .. code-block:: python
 
         REQUIRED_UTILITIES = ((ISecurityManager, '', SecurityManager, 'Security manager'),
-                              (IPrincipalAnnotationUtility, '', PrincipalAnnotationUtility, 'User profiles'))
+                              (IPrincipalAnnotationUtility, '', PrincipalAnnotationUtility,
+                               'User profiles'))
     """
     registry = get_current_registry()
     for interface, name, factory, default_id in utilities:
--- a/src/pyams_utils/size.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/size.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,7 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.size module
+
+This module provides a small function which can be used to convert
+a "size" value, given in bytes, to it's "human" representation.
+"""
 
 from babel import UnknownLocaleError
 from babel.core import Locale
@@ -18,6 +22,9 @@
 
 from pyams_utils.request import check_request
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_utils import _
 
 
--- a/src/pyams_utils/timezone/__init__.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/timezone/__init__.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,28 +10,32 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.timezone package
 
+All datetime values should be stored in UTC to avoid any problem.
+Then values can be displayed to users using a specific timezone; by default, used timezone
+is the one specified into server settings via an IServerTimezone utility which is created
+automatically when initializing a new site.
 
-# import standard library
+There is no easy way to get user's timezone from it's browser settings; so the more common
+choice is to let users define their timezone in their profile's settings.
+"""
+
 from datetime import datetime
 
 import pytz
-
-
-# import interfaces
-from pyams_utils.interfaces.timezone import IServerTimezone
 from pyramid.interfaces import IRequest
 from zope.interface.common.idatetime import ITZInfo
 
-# import packages
 from pyams_utils.adapter import adapter_config
+from pyams_utils.interfaces.timezone import IServerTimezone
 from pyams_utils.registry import query_utility
 
 
+__docformat__ = 'restructuredtext'
+
+
 GMT = pytz.timezone('GMT')
-_tz = pytz.timezone('Europe/Paris')
-tz = _tz
 
 
 @adapter_config(context=IRequest, provides=ITZInfo)
--- a/src/pyams_utils/unicode.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/unicode.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,16 +10,22 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.unicode module
+
+This module provides a small set of functions which can be used to handle unicode data and
+their bytes equivalent.
+"""
 
 import codecs
 import string
 
-
-_unicodeTransTable = {}
+__docformat__ = 'restructuredtext'
 
 
-def _fillUnicodeTransTable():
+_UNICODE_TRANS_TABLE = {}
+
+
+def _fill_unicode_trans_table():
     _corresp = [
         ("A", [0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x0100, 0x0102, 0x0104]),
         ("AE", [0x00C6]),
@@ -69,20 +75,22 @@
     ]
     for char, codes in _corresp:
         for code in codes:
-            _unicodeTransTable[code] = char
+            _UNICODE_TRANS_TABLE[code] = char
 
 
-_fillUnicodeTransTable()
+_fill_unicode_trans_table()
 
-removed_chars = '®©™…'
+
+_REMOVED_CHARS = '®©™…'
 """List of custom characters to remove from input strings"""
 
 
-def translate_string(s, escape_slashes=False, force_lower=True,
+def translate_string(value, escape_slashes=False, force_lower=True,
                      spaces=' ', remove_punctuation=True, keep_chars='_-.'):
+    # pylint: disable=too-many-arguments
     """Remove extended characters and diacritics from string and replace them with 'basic' ones
-    
-    :param str s: text to be cleaned.
+
+    :param str value: text to be translated
     :param boolean escape_slashes: if True, slashes are also converted
     :param boolean force_lower: if True, result is automatically converted to lower case
     :param str spaces: character used to replace spaces
@@ -91,39 +99,39 @@
     :return: text without diacritics or special characters
 
     >>> from pyams_utils.unicode import translate_string
-    >>> input = 'Ceci est un test en Français !!!'
-    >>> translate_string(input)
+    >>> input_string = 'Ceci est un test en Français !!!'
+    >>> translate_string(input_string)
     'ceci est un test en francais'
-    >>> translate_string(input, force_lower=False)
+    >>> translate_string(input_string, force_lower=False)
     'Ceci est un test en Francais'
-    >>> translate_string(input, spaces='-')
+    >>> translate_string(input_string, spaces='-')
     'ceci-est-un-test-en-francais'
-    >>> translate_string(input, remove_punctuation=False)
+    >>> translate_string(input_string, remove_punctuation=False)
     'ceci est un test en francais !!!'
-    >>> translate_string(input, keep_chars='!')
+    >>> translate_string(input_string, keep_chars='!')
     'ceci est un test en francais !!!'
     """
     if escape_slashes:
-        s = s.replace("\\", "/").split("/")[-1]
-    s = s.strip()
-    if isinstance(s, bytes):
-        s = s.decode("utf-8", "replace")
-    s = s.translate(_unicodeTransTable)
+        value = value.replace("\\", "/").split("/")[-1]
+    value = value.strip()
+    if isinstance(value, bytes):
+        value = value.decode("utf-8", "replace")
+    value = value.translate(_UNICODE_TRANS_TABLE)
     if remove_punctuation:
         punctuation = ''.join(filter(lambda x: x not in keep_chars,
-                                     string.punctuation + removed_chars))
-        s = ''.join(filter(lambda x: x not in punctuation, s))
+                                     string.punctuation + _REMOVED_CHARS))
+        value = ''.join(filter(lambda x: x not in punctuation, value))
     if force_lower:
-        s = s.lower()
-    s = s.strip()
+        value = value.lower()
+    value = value.strip()
     if spaces != ' ':
-        s = s.replace(' ', spaces)
-    return s
+        value = value.replace(' ', spaces)
+    return value
 
 
 def nvl(value, default=''):
     """Get specified value, or an empty string if value is empty
-    
+
     :param object value: value to be checked
     :param object default: default value to be returned if value is *false*
     :return: input value, or *default* if value is *false*
@@ -141,7 +149,7 @@
 
 def uninvl(value, default='', encoding='utf-8'):
     """Get specified value converted to unicode, or an empty unicode string if value is empty
-    
+
     :param str/bytes value: the input to be checked
     :param default: str; default value
     :param encoding: str; encoding name to use for conversion
@@ -161,13 +169,13 @@
         return value
     try:
         return codecs.decode(value or default, encoding)
-    except:
+    except ValueError:
         return codecs.decode(value or default, 'latin1')
 
 
 def unidict(value, encoding='utf-8'):
     """Get specified dict with values converted to unicode
-    
+
     :param dict value: input mapping of strings which may be converted to unicode
     :param str encoding: output encoding
     :return: dict; a new mapping with each value converted to unicode
@@ -186,7 +194,7 @@
 
 def unilist(value, encoding='utf-8'):
     """Get specified list with values converted to unicode
-    
+
     :param list value: input list of strings which may be converted to unicode
     :param str encoding: output encoding
     :return: list; a new list with each value converted to unicode
--- a/src/pyams_utils/vocabulary.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/vocabulary.py	Sat Nov 23 00:38:44 2019 +0100
@@ -15,8 +15,6 @@
 This module is used to handle vocabularies.
 """
 
-__docformat__ = 'restructuredtext'
-
 import logging
 
 import venusian
@@ -25,10 +23,13 @@
 from zope.schema.vocabulary import getVocabularyRegistry
 
 
-logger = logging.getLogger('PyAMS (utils)')
+__docformat__ = 'restructuredtext'
 
 
-class vocabulary_config:
+LOGGER = logging.getLogger('PyAMS (utils)')
+
+
+class vocabulary_config:  # pylint: disable=invalid-name
     """Class decorator to define a vocabulary
 
     :param str name: name of the registered vocabulary
@@ -48,7 +49,8 @@
             '''ZEO connections vocabulary'''
 
             def __init__(self, context=None):
-                terms = [SimpleTerm(name, title=util.name) for name, util in get_utilities_for(IZEOConnection)]
+                terms = [SimpleTerm(name, title=util.name)
+                         for name, util in get_utilities_for(IZEOConnection)]
                 super(ZEOConnectionVocabulary, self).__init__(terms)
 
     You can then use such a vocabulary in any schema field:
@@ -77,20 +79,21 @@
         settings = self.__dict__.copy()
         depth = settings.pop('_depth', 0)
 
-        def callback(context, name, ob):
-            logger.debug('Registering class {0} as vocabulary with name "{1}"'.format(str(ob), self.name))
-            directlyProvides(ob, IVocabularyFactory)
-            getVocabularyRegistry().register(self.name, ob)
+        def callback(context, name, obj):  # pylint: disable=unused-argument
+            LOGGER.debug('Registering class {0} as vocabulary with name "{1}"'.format(
+                str(obj), self.name))
+            directlyProvides(obj, IVocabularyFactory)
+            getVocabularyRegistry().register(self.name, obj)
 
         info = self.venusian.attach(wrapped, callback, category='pyams_vocabulary',
                                     depth=depth + 1)
 
-        if info.scope == 'class':
+        if info.scope == 'class':  # pylint: disable=no-member
             # if the decorator was attached to a method in a class, or
             # otherwise executed at class scope, we need to set an
             # 'attr' into the settings if one isn't already in there
             if settings.get('attr') is None:
                 settings['attr'] = wrapped.__name__
 
-        settings['_info'] = info.codeinfo  # fbo "action_method"
+        settings['_info'] = info.codeinfo  # pylint: disable=no-member
         return wrapped
--- a/src/pyams_utils/zodb.py	Wed Nov 20 19:40:26 2019 +0100
+++ b/src/pyams_utils/zodb.py	Sat Nov 23 00:38:44 2019 +0100
@@ -10,33 +10,34 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
+""""PyAMS_utils.zodb module
 
+This modules provides several utilities used to manage ZODB connections and persistent objects
+"""
 
-# import standard library
+from ZEO import DB
+from ZODB.interfaces import IConnection
+from persistent import Persistent
+from persistent.interfaces import IPersistent
+from pyramid.events import subscriber
+from pyramid_zodbconn import db_from_uri, get_uris
+from transaction.interfaces import ITransactionManager
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.container.contained import Contained
+from zope.interface import implementer
+from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+from zope.schema import getFieldNames
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
-# import interfaces
-from persistent.interfaces import IPersistent
+from pyams_utils.adapter import adapter_config
 from pyams_utils.interfaces.site import IOptionalUtility
 from pyams_utils.interfaces.zeo import IZEOConnection
-from transaction.interfaces import ITransactionManager
-from ZODB.interfaces import IConnection
-from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+from pyams_utils.registry import get_global_registry, get_utilities_for
+from pyams_utils.vocabulary import vocabulary_config
 
-# import packages
-from persistent import Persistent
-from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import get_utilities_for, get_global_registry
-from pyams_utils.vocabulary import vocabulary_config
-from pyramid.events import subscriber
-from pyramid_zodbconn import get_uris, db_from_uri
-from ZEO import DB
-from zope.container.contained import Contained
-from zope.interface import implementer
-from zope.schema import getFieldNames
-from zope.schema.fieldproperty import FieldProperty
-from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+__docformat__ = 'restructuredtext'
 
 
 @adapter_config(context=IPersistent, provides=IConnection)
@@ -53,19 +54,20 @@
         cur = getattr(cur, '__parent__', None)
         if cur is None:
             return None
-    return cur._p_jar
+    return cur._p_jar  # pylint: disable=protected-access
 
 
 # IPersistent adapters copied from zc.twist package
 # also register this for adapting from IConnection
 @adapter_config(context=IPersistent, provides=ITransactionManager)
 def persistent_transaction_manager(obj):
+    """Transaction manager adapter for persistent objects"""
     conn = IConnection(obj)  # typically this will be
                              # zope.keyreference.persistent.connectionOfPersistent
     try:
         return conn.transaction_manager
     except AttributeError:
-        return conn._txn_mgr
+        return conn._txn_mgr  # pylint: disable=protected-access
         # or else we give up; who knows.  transaction_manager is the more
         # recent spelling.
 
@@ -75,13 +77,13 @@
 #
 
 @implementer(IZEOConnection)
-class ZEOConnection(object):
+class ZEOConnection:
     """ZEO connection object
 
     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.
+    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.
 
     Note that a ZEO connection object is a context manager, so you can use it like this:
 
@@ -157,6 +159,7 @@
 
     @property
     def connection(self):
+        """Connection getter"""
         return self._connection
 
     # Context manager methods
@@ -195,21 +198,22 @@
 class ZEOConnectionVocabulary(SimpleVocabulary):
     """ZEO connections vocabulary"""
 
-    def __init__(self, context=None):
-        terms = [SimpleTerm(name, title=util.name) for name, util in get_utilities_for(IZEOConnection)]
+    def __init__(self, context=None):  # pylint: disable=unused-argument
+        terms = [SimpleTerm(name, title=util.name)
+                 for name, util in get_utilities_for(IZEOConnection)]
         super(ZEOConnectionVocabulary, self).__init__(terms)
 
 
 def get_connection_from_settings(settings=None):
     """Load connection matching registry settings"""
     if settings is None:
-        settings = get_global_registry().settings
+        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()
 
 
-class ZODBConnection(object):
+class ZODBConnection:
     """ZODB connection wrapper
 
     Connections are extracted from Pyramid's settings file in *zodbconn.uri* entries.
@@ -231,7 +235,7 @@
     def __init__(self, name='', settings=None):
         self.name = name or ''
         if not settings:
-            settings = get_global_registry().settings
+            settings = get_global_registry().settings  # pylint: disable=no-member
         self.settings = settings
 
     _connection = None
@@ -240,14 +244,17 @@
 
     @property
     def connection(self):
+        """Connection getter"""
         return self._connection
 
     @property
     def db(self):
+        """Database getter"""
         return self._db
 
     @property
     def storage(self):
+        """Storage getter"""
         return self._storage
 
     def get_connection(self):
@@ -259,8 +266,10 @@
                 self._db = connection.db()
                 self._storage = self.db.storage
                 return connection
+        return None
 
     def close(self):
+        """Connection close"""
         self._connection.close()
         self._db.close()
         self._storage.close()
@@ -278,16 +287,16 @@
 class ZODBConnectionVocabulary(SimpleVocabulary):
     """ZODB connections vocabulary"""
 
-    def __init__(self, context=None):
-        settings = get_global_registry().settings
+    def __init__(self, context=None):  # pylint: disable=unused-argument
+        settings = get_global_registry().settings  # pylint: disable=no-member
         terms = [SimpleTerm(name, title=name) for name, uri in get_uris(settings)]
         super(ZODBConnectionVocabulary, self).__init__(terms)
 
 
-volatile_marker = object()
+VOLATILE_MARKER = object()
 
 
-class volatile_property:
+class volatile_property:  # pylint: disable=invalid-name
     """Property decorator to define volatile attributes into persistent classes"""
 
     def __init__(self, fget, doc=None):
@@ -300,8 +309,8 @@
         if inst is None:
             return self
         attrname = '_v_{0}'.format(self.__name__)
-        value = getattr(inst, attrname, volatile_marker)
-        if value is volatile_marker:
+        value = getattr(inst, attrname, VOLATILE_MARKER)
+        if value is VOLATILE_MARKER:
             value = self.fget(inst)
             setattr(inst, attrname, value)
         return value