Updated documentation
authorThierry Florac <thierry.florac@onf.fr>
Fri, 18 Nov 2016 15:28:54 +0100
changeset 72 9049384a2bd4
parent 71 01d01045a2b7
child 73 d70e1629442b
Updated documentation
docs/HISTORY.txt
docs/Makefile
docs/make.bat
docs/source/conf.py
docs/source/index.rst
docs/source/install.rst
docs/source/modules.rst
docs/source/pyams_utils.interfaces.rst
docs/source/pyams_utils.protocol.rst
docs/source/pyams_utils.rst
docs/source/pyams_utils.scripts.rst
docs/source/pyams_utils.tests.rst
docs/source/pyams_utils.timezone.rst
docs/source/pyams_utils.widget.rst
docs/source/pyams_utils.zmi.rst
docs/source/site.rst
docs/source/traverser.rst
docs/source/zca.rst
src/pyams_utils/adapter.py
src/pyams_utils/attr.py
src/pyams_utils/container.py
src/pyams_utils/context.py
src/pyams_utils/data.py
src/pyams_utils/date.py
src/pyams_utils/encoding.py
src/pyams_utils/html.py
src/pyams_utils/interfaces/__init__.py
src/pyams_utils/intids.py
src/pyams_utils/list.py
src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo
src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po
src/pyams_utils/locales/pyams_utils.pot
src/pyams_utils/lock.py
src/pyams_utils/progress.py
src/pyams_utils/property.py
src/pyams_utils/protocol/http.py
src/pyams_utils/registry.py
src/pyams_utils/request.py
src/pyams_utils/session.py
src/pyams_utils/site.py
src/pyams_utils/size.py
src/pyams_utils/tales.py
src/pyams_utils/text.py
src/pyams_utils/timezone/utility.py
src/pyams_utils/traversing.py
src/pyams_utils/unicode.py
src/pyams_utils/url.py
src/pyams_utils/vocabulary.py
src/pyams_utils/wsgi.py
src/pyams_utils/zodb.py
--- a/docs/HISTORY.txt	Tue Nov 15 10:43:55 2016 +0100
+++ b/docs/HISTORY.txt	Fri Nov 18 15:28:54 2016 +0100
@@ -1,6 +1,16 @@
 Changelog
 =========
 
+0.1.4
+-----
+ - added "condition" optional argument to "get_parent" traversing helper to retrieve a parent only if given function
+   returns a "True" value when called with parent as argument
+ - added annotation for vocabulary registry
+ - added 'prefix' argument to 'request_property' and 'session_property' decorators
+ - handle POSError in 'query_utility' registry function
+ - updated unit tests
+ - updated documentation
+
 0.1.3
 -----
  - corrected XML-RPC client for Python 3
@@ -14,3 +24,7 @@
 0.1.1
 -----
  - corrected cookies management in XML-RPC authenticated transport
+
+0.1.0
+-----
+ - initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Makefile	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,225 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = ../bin/py ../bin/sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  applehelp  to make an Apple Help Book"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  epub3      to make an epub3"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  coverage   to run coverage check of the documentation (if enabled)"
+	@echo "  dummy      to check syntax errors of document sources"
+
+.PHONY: clean
+clean:
+	rm -rf $(BUILDDIR)/*
+
+.PHONY: html
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+.PHONY: dirhtml
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+.PHONY: singlehtml
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+.PHONY: pickle
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+.PHONY: json
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+.PHONY: htmlhelp
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+.PHONY: qthelp
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyAMS_utils.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyAMS_utils.qhc"
+
+.PHONY: applehelp
+applehelp:
+	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+	@echo
+	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+	@echo "N.B. You won't be able to view it unless you put it in" \
+	      "~/Library/Documentation/Help or install it in your application" \
+	      "bundle."
+
+.PHONY: devhelp
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/PyAMS_utils"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyAMS_utils"
+	@echo "# devhelp"
+
+.PHONY: epub
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+.PHONY: epub3
+epub3:
+	$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
+	@echo
+	@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
+
+.PHONY: latex
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+.PHONY: latexpdf
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: latexpdfja
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: text
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+.PHONY: man
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+.PHONY: texinfo
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+.PHONY: info
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+.PHONY: gettext
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+.PHONY: changes
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+.PHONY: linkcheck
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+.PHONY: doctest
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+.PHONY: coverage
+coverage:
+	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+	@echo "Testing of coverage in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/coverage/python.txt."
+
+.PHONY: xml
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+.PHONY: pseudoxml
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+
+.PHONY: dummy
+dummy:
+	$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
+	@echo
+	@echo "Build finished. Dummy builder generates no files."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/make.bat	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,281 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+set I18NSPHINXOPTS=%SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  epub3      to make an epub3
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  texinfo    to make Texinfo files
+	echo.  gettext    to make PO message catalogs
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  xml        to make Docutils-native XML files
+	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	echo.  coverage   to run coverage check of the documentation if enabled
+	echo.  dummy      to check syntax errors of document sources
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+
+REM Check if sphinx-build is available and fallback to Python version if any
+%SPHINXBUILD% 1>NUL 2>NUL
+if errorlevel 9009 goto sphinx_python
+goto sphinx_ok
+
+:sphinx_python
+
+set SPHINXBUILD=python -m sphinx.__init__
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.http://sphinx-doc.org/
+	exit /b 1
+)
+
+:sphinx_ok
+
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyAMS_utils.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyAMS_utils.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "epub3" (
+	%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "latexpdf" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	cd %BUILDDIR%/latex
+	make all-pdf
+	cd %~dp0
+	echo.
+	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "latexpdfja" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	cd %BUILDDIR%/latex
+	make all-pdf-ja
+	cd %~dp0
+	echo.
+	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "texinfo" (
+	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+	goto end
+)
+
+if "%1" == "gettext" (
+	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+if "%1" == "coverage" (
+	%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of coverage in the sources finished, look at the ^
+results in %BUILDDIR%/coverage/python.txt.
+	goto end
+)
+
+if "%1" == "xml" (
+	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The XML files are in %BUILDDIR%/xml.
+	goto end
+)
+
+if "%1" == "pseudoxml" (
+	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
+	goto end
+)
+
+if "%1" == "dummy" (
+	%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. Dummy builder generates no files.
+	goto end
+)
+
+:end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/conf.py	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# PyAMS_utils documentation build configuration file, created by
+# sphinx-quickstart on Tue Nov 15 16:18:42 2016.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'PyAMS_utils'
+copyright = '2016, Thierry Florac <tflorac@ulthar.net>'
+author = 'Thierry Florac <tflorac@ulthar.net>'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1.4'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#
+# today = ''
+#
+# Else, today_fmt is used as the format for a strftime call.
+#
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'pyramid'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents.
+# "<project> v<release> documentation" by default.
+#
+# html_title = 'PyAMS_utils v0.1.4'
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#
+# html_logo = None
+
+# The name of an image file (relative to this directory) to use as a favicon of
+# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#
+# html_extra_path = []
+
+# If not None, a 'Last updated on:' timestamp is inserted at every page
+# bottom, using the given strftime format.
+# The empty string is equivalent to '%b %d, %Y'.
+#
+# html_last_updated_fmt = None
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+#
+# html_domain_indices = True
+
+# If false, no index is generated.
+#
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+#   'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
+#   'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
+#
+# html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# 'ja' uses this config value.
+# 'zh' user can custom change `jieba` dictionary path.
+#
+# html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#
+# html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PyAMS_utilsdoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+     # The paper size ('letterpaper' or 'a4paper').
+     #
+     # 'papersize': 'letterpaper',
+
+     # The font size ('10pt', '11pt' or '12pt').
+     #
+     # 'pointsize': '10pt',
+
+     # Additional stuff for the LaTeX preamble.
+     #
+     # 'preamble': '',
+
+     # Latex figure (float) alignment
+     #
+     # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'PyAMS_utils.tex', 'PyAMS\\_utils Documentation',
+     'Thierry Florac \\textless{}tflorac@ulthar.net\\textgreater{}', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+#
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#
+# latex_appendices = []
+
+# It false, will not define \strong, \code, 	itleref, \crossref ... but only
+# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
+# packages.
+#
+# latex_keep_old_macro_names = True
+
+# If false, no module index is generated.
+#
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'pyams_utils', 'PyAMS_utils Documentation',
+     [author], 1)
+]
+
+# If true, show URL addresses after external links.
+#
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'PyAMS_utils', 'PyAMS_utils Documentation',
+     author, 'PyAMS_utils', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+#
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#
+# texinfo_no_detailmenu = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/index.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,47 @@
+.. PyAMS_utils documentation master file, created by
+   sphinx-quickstart on Tue Nov 15 16:18:42 2016.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+.. _index:
+
+
+Welcome to PyAMS_utils's documentation!
+=======================================
+
+At first, PyAMS was "Pyramid Application Management Skin". Actually, it's not only a simple skin but a whole set of
+applications and content management tools.
+
+PyAMS_utils is a multipurpose utilities package, providing tools including:
+
+ - custom interfaces
+
+ - custom ZCA registry annotations
+
+ - local registry support
+
+ - network protocols utilities (for HTTP and XML-RPC)
+
+ - custom utilities
+
+ - a command line script to handle database upgrade process
+
+
+.. toctree::
+   :maxdepth: 2
+
+   install
+   zca
+   site
+   traverser
+   tales
+   locks
+   utilities
+
+
+Indices and tables
+------------------
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/install.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,139 @@
+.. _install:
+
+Installing PyAMS
+================
+
+PyAMS default installation is based on `Buildout <http://www.buildout.org>`_ utility. It's not mandatory to use a
+virtual environment, but it allows you to have a better control over your Python resources.
+
+Current PyAMS version is based and validated for Python 3.4; your Python environment must also include a C
+compiler as well as development headers for Python, *libjpeg*, *libpng*, *libfreetype*, *libxml2*, *libxslt* and
+eventually *libldap* or *libzmq*.
+
+PyAMS default components configuration also pre-suppose that the following external tools are available:
+
+- a *memcached* server, to store sessions and cache (can be changed through Beaker configuration)
+
+Optional tools also include:
+
+- an *ElasticSearch* server for full text indexing (see *PyAMS_content_es* package)
+
+- a *WebSockets* server using async IO. This is used to manage notifications (see *PyAMS_notify* and *PyAMS_notify_ws*
+  packages). An *out of the box* environment can be built using *pyams_asyncio* scaffold provided by *pyams_base*
+  package.
+
+
+You can also choose to use a local ZODB instance, or a ZEO server (which can be local or remote, but is required if
+you want to use PyAMS in a muti-processes configuration).
+
+
+Creating initial buildout
+-------------------------
+
+PyAMS provides a new Pyramid scaffold, called *pyams*, provided by the *pyams_base* package.
+
+A simple option to install PyAMS is to create a buildout environment including *Pyramid* and *PyAMS_base* packages:
+
+.. code-block:: bash
+
+    # mkdir /var/local/env/
+    # pip3 install virtualenv
+    # virtualenv --python=python3.4 pyams
+    # cd pyams
+    # . bin/activate
+    (pyams) # pip3.4 install zc.buildout
+    (pyams) # buildout init
+
+Then update your Buildout configuration file *buildout.cfg* as follow:
+
+.. code-block:: ini
+
+    [buildout]
+    find-links = http://download.ztfy.org/eggs
+    extends = http://download.ztfy.org/pyams/pyams-0.1.0.cfg
+    socket-timeout = 3
+    show-picked-versions = true
+    newest = false
+    allow-hosts =
+        *.python.org
+        *.sourceforge.net
+        github.com
+        bitbucket.org
+    versions = versions
+    eggs-directory = eggs
+    parts = pyramid
+
+    [pyramid]
+    recipe = zc.recipe.egg
+    dependent-scripts = true
+    eggs =
+        pyramid
+        pyams_base
+    interpreter = py3.4
+
+Then launch the buildout initialization:
+
+.. code-block:: bash
+
+    (pyams) # ./bin/buildout
+    (pyams) # ./bin/pcreate -l
+    Available scaffolds:
+      alchemy:  Pyramid project using SQLAlchemy, SQLite, URL dispatch, and
+      pyams:    Pyramid project using all PyAMS packages
+      starter:  Pyramid starter project using URL dispatch and Chameleon
+      zodb:     Pyramid project using ZODB, traversal, and Chameleon
+    (pyams) # ./bin/pcreate -t pyams myapp
+    (pyams) # cd myapp
+
+You can then check, and eventually update, the proposed Buildout configuration file *buildout.cfg*, to add or remove
+packages or update settings to your needs. Then finalize Bootstrap initialization:
+
+.. code-block:: bash
+
+    (pyams) # ../bin/buildout bootstrap
+    (pyams) # ./bin/buildout
+
+This last operation can be quite long, as many packages have to downloaded, compiled and installed in the virtual
+environment. If you encounter any compile error, just install the required dependencies and restart the buildout.
+
+
+Environment settings
+--------------------
+
+The project generated from *pyams* scaffold is based on default Pyramid's *zodb* scaffold, but it adds:
+
+- a custom application factory, in the *webapp* directory (see :ref:`site`)
+
+- a set of directories to store runtime data, in the *var* directory; each directory contains a *README.txt* file
+  which should be self-explanatory to indicate what this directory should contain, including a ZODB or a ZEO cache
+
+- a set of configuration files, in the *etc* directory; here are standard *development.ini* and *production.ini*
+  configuration files, two ZODB configuration files (*zodb.conf.fs* for a single FileStorage application process, and
+  *zodb.conf.zeo* for a ZEO client storage; default configuration defined in INI files is based on a single file
+  storage) and two Apache configurations (for Apache 2.2 and 2.4) using *mod_wsgi*.
+
+Once the project have been created from the scaffold, you are free to update all the configuration files.
+
+If you need to add packages to the environment, you have to add them to the *buildout.cfg* file **AND** to the INI
+file (in the *pyramid.includes* section) before running the *buildout* another time; don't forget to add the
+requested version at the end of *buildout.cfg* file, as Buildout is not configured by default to automatically
+download the last release of a given unknown package.
+
+*development.ini* and *production.ini* files contain many commented directives related to PyAMS components. Read and
+update them carefully before initializing your application database!
+
+
+Initializing the database
+-------------------------
+
+When you have downloaded and installed all required packages, you have to initialize the database so that all
+required components are available.
+
+From a shell, just type:
+
+.. code-block:: bash
+
+    (pyams) # ./bin/pyams_upgrade etc/development.ini
+
+This process requires that every package is correctly included into *pyramid.includes* directive from selected
+configuration file.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/modules.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,7 @@
+pyams_utils
+===========
+
+.. toctree::
+   :maxdepth: 4
+
+   pyams_utils
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.interfaces.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,94 @@
+pyams_utils.interfaces package
+==============================
+
+Submodules
+----------
+
+pyams_utils.interfaces.data module
+----------------------------------
+
+.. automodule:: pyams_utils.interfaces.data
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.intids module
+------------------------------------
+
+.. automodule:: pyams_utils.interfaces.intids
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.site module
+----------------------------------
+
+.. automodule:: pyams_utils.interfaces.site
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.size module
+----------------------------------
+
+.. automodule:: pyams_utils.interfaces.size
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.tales module
+-----------------------------------
+
+.. automodule:: pyams_utils.interfaces.tales
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.text module
+----------------------------------
+
+.. automodule:: pyams_utils.interfaces.text
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.timezone module
+--------------------------------------
+
+.. automodule:: pyams_utils.interfaces.timezone
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.traversing module
+----------------------------------------
+
+.. automodule:: pyams_utils.interfaces.traversing
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.tree module
+----------------------------------
+
+.. automodule:: pyams_utils.interfaces.tree
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.interfaces.zeo module
+---------------------------------
+
+.. automodule:: pyams_utils.interfaces.zeo
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.interfaces
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.protocol.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,30 @@
+pyams_utils.protocol package
+============================
+
+Submodules
+----------
+
+pyams_utils.protocol.http module
+--------------------------------
+
+.. automodule:: pyams_utils.protocol.http
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.protocol.xmlrpc module
+----------------------------------
+
+.. automodule:: pyams_utils.protocol.xmlrpc
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.protocol
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,267 @@
+pyams_utils package
+===================
+
+Subpackages
+-----------
+
+.. toctree::
+
+    pyams_utils.interfaces
+    pyams_utils.protocol
+    pyams_utils.scripts
+    pyams_utils.tests
+    pyams_utils.timezone
+    pyams_utils.widget
+    pyams_utils.zmi
+
+Submodules
+----------
+
+pyams_utils.adapter module
+--------------------------
+
+.. automodule:: pyams_utils.adapter
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.attr module
+-----------------------
+
+.. automodule:: pyams_utils.attr
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.container module
+----------------------------
+
+.. automodule:: pyams_utils.container
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.context module
+--------------------------
+
+.. automodule:: pyams_utils.context
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.data module
+-----------------------
+
+.. automodule:: pyams_utils.data
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.date module
+-----------------------
+
+.. automodule:: pyams_utils.date
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.decorator module
+----------------------------
+
+.. automodule:: pyams_utils.decorator
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.encoding module
+---------------------------
+
+.. automodule:: pyams_utils.encoding
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.html module
+-----------------------
+
+.. automodule:: pyams_utils.html
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.i18n module
+-----------------------
+
+.. automodule:: pyams_utils.i18n
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.include module
+--------------------------
+
+.. automodule:: pyams_utils.include
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.intids module
+-------------------------
+
+.. automodule:: pyams_utils.intids
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.list module
+-----------------------
+
+.. automodule:: pyams_utils.list
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.lock module
+-----------------------
+
+.. automodule:: pyams_utils.lock
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.progress module
+---------------------------
+
+.. automodule:: pyams_utils.progress
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.property module
+---------------------------
+
+.. automodule:: pyams_utils.property
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.registry module
+---------------------------
+
+.. automodule:: pyams_utils.registry
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.request module
+--------------------------
+
+.. automodule:: pyams_utils.request
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.schema module
+-------------------------
+
+.. automodule:: pyams_utils.schema
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.session module
+--------------------------
+
+.. automodule:: pyams_utils.session
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.site module
+-----------------------
+
+.. automodule:: pyams_utils.site
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.size module
+-----------------------
+
+.. automodule:: pyams_utils.size
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.tales module
+------------------------
+
+.. automodule:: pyams_utils.tales
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.text module
+-----------------------
+
+.. automodule:: pyams_utils.text
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.traversing module
+-----------------------------
+
+.. automodule:: pyams_utils.traversing
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.unicode module
+--------------------------
+
+.. automodule:: pyams_utils.unicode
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.url module
+----------------------
+
+.. automodule:: pyams_utils.url
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.vocabulary module
+-----------------------------
+
+.. automodule:: pyams_utils.vocabulary
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.wsgi module
+-----------------------
+
+.. automodule:: pyams_utils.wsgi
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.zodb module
+-----------------------
+
+.. automodule:: pyams_utils.zodb
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.scripts.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,22 @@
+pyams_utils.scripts package
+===========================
+
+Submodules
+----------
+
+pyams_utils.scripts.zodb module
+-------------------------------
+
+.. automodule:: pyams_utils.scripts.zodb
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.scripts
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.tests.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,30 @@
+pyams_utils.tests package
+=========================
+
+Submodules
+----------
+
+pyams_utils.tests.test_utilsdocs module
+---------------------------------------
+
+.. automodule:: pyams_utils.tests.test_utilsdocs
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.tests.test_utilsdocstrings module
+---------------------------------------------
+
+.. automodule:: pyams_utils.tests.test_utilsdocstrings
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.tests
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.timezone.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,30 @@
+pyams_utils.timezone package
+============================
+
+Submodules
+----------
+
+pyams_utils.timezone.utility module
+-----------------------------------
+
+.. automodule:: pyams_utils.timezone.utility
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.timezone.vocabulary module
+--------------------------------------
+
+.. automodule:: pyams_utils.timezone.vocabulary
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.timezone
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.widget.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,22 @@
+pyams_utils.widget package
+==========================
+
+Submodules
+----------
+
+pyams_utils.widget.decimal module
+---------------------------------
+
+.. automodule:: pyams_utils.widget.decimal
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.widget
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/pyams_utils.zmi.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,38 @@
+pyams_utils.zmi package
+=======================
+
+Submodules
+----------
+
+pyams_utils.zmi.intids module
+-----------------------------
+
+.. automodule:: pyams_utils.zmi.intids
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.zmi.timezone module
+-------------------------------
+
+.. automodule:: pyams_utils.zmi.timezone
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+pyams_utils.zmi.zeo module
+--------------------------
+
+.. automodule:: pyams_utils.zmi.zeo
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+
+Module contents
+---------------
+
+.. automodule:: pyams_utils.zmi
+    :members:
+    :undoc-members:
+    :show-inheritance:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/site.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,73 @@
+.. _site:
+
+PyAMS site management
+=====================
+
+PyAMS site management is based on the ZODB.
+
+On application startup, if PyAMS_utils package is included into Pyramid configuration, several operations take
+place:
+
+ - a custom **site factory** is defined
+
+ - custom request methods are defined
+
+ - a custom **traverser** handling **namespaces** is defined
+
+ - a custom subscribers predicate based on interfaces support is defined
+
+ - several adapters are registered, to handle annotations and key references
+
+ - custom TALES extensions are registered.
+
+The site factory is an important component in this process. It is this factory which will define the application root
+and create a **local site manager**.
+
+Pyramid application is loaded from ZODB's root via a key defined in Pyramid's configuration file; the key is named
+*pyams.application_name* and it's default value is *application*.
+
+If the application can't be found, PyAMS is looking for an application class name in Pyramid's configuration file; the
+class name configuration key is called *pyams.application_factory* and defined by default as
+*pyams_utils.site.BaseSiteRoot*. PyAMS default site factory will then create the application, and add a local site
+manager to it (see :ref:`zca`).
+
+After application creation, a :py:class:`NewLocalSiteCreatedEvent <pyams_utils.site.NewLocalSiteCreatedEvent>` is
+notified. Custom packages can subscribe to this event to register custom components.
+
+
+*pyams_upgrade* command line script
+-----------------------------------
+
+Pyramid allows to define custom command line scripts for application management. A script called *pyams_upgrade* is
+provided by PyAMS_utils package; this script apply the same process as PyAMS site factory, but can also be used to
+manage **database generations**. The idea behind this is just to allow custom packages to provide a way to check and
+upgrade database configuration away from application startup process:
+
+.. code-block:: bash
+
+    # ./bin/pyams_upgrade webapp/development.ini
+
+
+A **site generation checker** is just a named utility providing :py:class:`pyams_utils.interfaces.site.ISiteGenerations`
+interface. For example, **pyams_security** package provides such utility, to make sure that local site manager
+contains a PyAMS security manager and a principal annotation utility:
+
+.. code-block:: python
+
+    from pyams_utils.site import check_required_utilities
+
+    REQUIRED_UTILITIES = ((ISecurityManager, '', SecurityManager, 'Security manager'),
+                          (IPrincipalAnnotationUtility, '', PrincipalAnnotationUtility, 'User profiles'))
+
+    @utility_config(name='PyAMS security', provides=ISiteGenerations)
+    class SecurityGenerationsChecker(object):
+    """I18n generations checker"""
+
+        generation = 1
+
+        def evolve(self, site, current=None):
+            """Check for required utilities"""
+            check_required_utilities(site, REQUIRED_UTILITIES)
+
+:py:func:`check_required_utilities <pyams_utils.site.check_required_utilities>` is a PyAMS_utils utility function which
+can to used to verify that a set of local utilities are correctly registered with the given names and interfaces.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/traverser.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,47 @@
+.. _traverser:
+
+PyAMS namespace traverser
+=========================
+
+PyAMS_utils provide a custom URL traverser, defined in package :py:mod:`pyams_utils.traversing`.
+
+The :py:class:`NamespaceTraverser <pyams_utils.traversing.NamespaceTraverser>` is a custom traverser based on default
+Pyramid's *ResourceTreeAdapter*, but it adds the ability to use *namespaces*. Inherited from *Zope3* concept, a
+namespace is a resource path element starting with the « *++* » characters, like this:
+
+.. code-block:: none
+
+    http://localhost:5432/folder/content/++ns++argument/@@view.html
+
+In this sample, *ns* is the namespace name. When the traverser detects a namespace, it looks for several named
+adapters (or multi-adapters) to the :py:class:`ITraversable <zope.traversing.interfaces.ITraversable>` interface
+defined in *zope.traversing* package. Adapters lookup with name *ns* is done for the current *context* and *request*,
+then only for the context and finally for the request, in this order. If a traversing adapter is found, it's
+:py:func:`traverse` method is called, with the *attr* value as first argument, and the rest of the traversal stack
+as second one.
+
+This is for example how a custom *etc* namespace traverser is defined:
+
+.. code-block:: python
+
+    from pyams_utils.interfaces.site import ISiteRoot
+    from zope.traversing.interfaces import ITraversable
+
+    from pyams_utils.adapter import adapter_config, ContextAdapter
+
+    @adapter_config(name='etc', context=ISiteRoot, provides=ITraversable)
+    class SiteRootEtcTraverser(ContextAdapter):
+        """Site root ++etc++ namespace traverser"""
+
+        def traverse(self, name, furtherpath=None):
+            if name == 'site':
+                return self.context.getSiteManager()
+            raise NotFound
+
+By using an URL like '++etc++site' on your site root, you can then get access to your local site manager.
+
+*argument* is not mandatory for the namespace traverser. If it is not provided, the *traverse* method is called with
+an empty string (with is a default adapter name) as first argument.
+
+Several PyAMS components use custom traversal adapters. For example, getting thumbnails from an image is done
+through a traversing adapter, which results in nicer URLs than when using classic URLs with arguments...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/source/zca.rst	Fri Nov 18 15:28:54 2016 +0100
@@ -0,0 +1,262 @@
+.. _zca:
+
+Managing ZCA with PyAMS
+=======================
+
+The **Zope Component Architecture** (aka ZCA) is used by the Pyramid framework "under the hood" to handle interfaces,
+adapters and utilities. You don't **have to** use it in your own applications. But you can.
+
+The ZCA is mainly adding elements like **interfaces**, **adapters** and **utilities** to the Python language. It
+allows you to write a framework or an application by using **components** which can be extended easily.
+
+You will find several useful resources about ZCA concepts on the internet.
+
+
+Local utilities
+---------------
+
+In ZCA, a **utility** is a **registered** component which provides an **interface**. This interface is the
+**contract** which defines features provided by the component which implements it.
+
+When a Pyramid application starts, a **global registry** is created to register a whole set of utilities and
+adapters; this registration can be done via ZCML directives or via native Python code.
+In addition, PyAMS allows you to define **local utilities**, which are stored and registered in the ZODB via a **site
+manager**.
+
+
+Defining site root
+------------------
+
+One of PyAMS pre-requisites is to use the ZODB, at least to store the site root application, it's configuration and a
+set of local utilities. :ref:`site` describes application startup and **local site manager**
+initialization process.
+
+This site can be used to store **local utilities** whose configuration, which is easily available to site
+administrators through management interface, is stored in the ZODB.
+
+
+Registering global utilities
+----------------------------
+
+**Global utilities** are components providing an interface which are registered in the global registry.
+PyAMS_utils package provides custom annotations to register global utilities without using ZCML. For example, a skin
+is nothing more than a simple utility providing the *ISkin* interface:
+
+.. code-block:: python
+
+    from pyams_default_theme.layer import IPyAMSDefaultLayer
+    from pyams_skin.interfaces import ISkin
+    from pyams_utils.registry import utility_config
+
+    @utility_config(name='PyAMS default skin', provides=ISkin)
+    class PyAMSDefaultSkin(object):
+        """PyAMS default skin"""
+
+        label = _("PyAMS default skin")
+        layer = IPyAMSDefaultLayer
+
+This annotation registers a utility, named *PyAMS default skin*, providing the *ISkin* interface. It's the developer
+responsibility to provide all attributes and methods required by the provided interface.
+
+
+Registering local utilities
+---------------------------
+
+A local utility is a persistent object, registered in a *local site manager*, and providing a specific interface (if
+a component provides several interfaces, it can be registered several times).
+
+Some components can be required by a given package, and created automatically via the *pyams_upgrade* command line
+script; this process relies on the *ISiteGenerations* interface, for example for the timezone utility, a component
+provided by PyAMS_utils package to handle server timezone and display times correctly:
+
+.. code-block:: python
+
+    from pyams_utils.interfaces.site import ISiteGenerations
+    from pyams_utils.interfaces.timezone import IServerTimezone
+
+    from persistent import Persistent
+    from pyams_utils.registry import utility_config
+    from pyams_utils.site import check_required_utilities
+    from pyramid.events import subscriber
+    from zope.container.contained import Contained
+    from zope.interface import implementer
+    from zope.schema.fieldproperty import FieldProperty
+
+    @implementer(IServerTimezone)
+    class ServerTimezoneUtility(Persistent, Contained):
+
+        timezone = FieldProperty(IServerTimezone['timezone'])
+
+    REQUIRED_UTILITIES = ((IServerTimezone, '', ServerTimezoneUtility, 'Server timezone'),)
+
+    @subscriber(INewLocalSite)
+    def handle_new_local_site(event):
+        """Create a new ServerTimezoneUtility when a site is created"""
+        site = event.manager.__parent__
+        check_required_utilities(site, REQUIRED_UTILITIES)
+
+    @utility_config(name='PyAMS timezone', provides=ISiteGenerations)
+    class TimezoneGenerationsChecker(object):
+        """Timezone generations checker"""
+
+        generation = 1
+
+        def evolve(self, site, current=None):
+            """Check for required utilities"""
+            check_required_utilities(site, REQUIRED_UTILITIES)
+
+Some utilities can also be created manually by an administrator through the management interface, and registered
+automatically after their creation. For example, this is how a ZEO connection utility (which is managing settings to
+define a ZEO connection) is registered:
+
+.. code-block:: python
+
+    from pyams_utils.interfaces.site import IOptionalUtility
+    from pyams_utils.interfaces.zeo import IZEOConnection
+    from zope.annotation.interfaces import IAttributeAnnotatable
+    from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
+
+    from persistent import Persistent
+    from pyramid.events import subscriber
+    from zope.container.contained import Contained
+
+    @implementer(IZEOConnection)
+    class ZEOConnection(object):
+        """ZEO connection object. See source code to get full implementation..."""
+
+    @implementer(IOptionalUtility, IAttributeAnnotatable)
+    class ZEOConnectionUtility(ZEOConnection, Persistent, Contained):
+        """Persistent ZEO connection utility"""
+
+    @subscriber(IObjectAddedEvent, context_selector=IZEOConnection)
+    def handle_added_connection(event):
+        """Register new ZEO connection when added"""
+        manager = event.newParent
+        manager.registerUtility(event.object, IZEOConnection, name=event.object.name)
+
+    @subscriber(IObjectRemovedEvent, context_selector=IZEOConnection)
+    def handle_removed_connection(event):
+        """Un-register ZEO connection when deleted"""
+        manager = event.oldParent
+        manager.unregisterUtility(event.object, IZEOConnection, name=event.object.name)
+
+*context_selector* is a custom subscriber predicate, so that subscriber event is activated only if object concerned
+by an event is providing given interface.
+
+
+Looking for utilities
+---------------------
+
+ZCA provides the *getUtility* and *queryUtility* functions to look for a utility. But these methods only applies to
+global registry.
+
+PyAMS package provides equivalent functions, which are looking for components into local registry before looking into
+the global one. For example:
+
+.. code-block:: python
+
+    from pyams_security.interfaces import ISecurityManager
+    from pyams_utils.registry import query_utility
+
+    manager = query_utility(ISecurityManager)
+    if manager is not None:
+        print("Manager is there!")
+
+All ZCA utility functions have been ported to use local registry: *registered_utilities*, *query_utility*,
+*get_utility*, *get_utilities_for*, *get_all_utilities_registered_for* functions all follow the equivalent ZCA
+functions API, but are looking for utilities in the local registry before looking in the global registry.
+
+
+Registering adapters
+--------------------
+
+An adapter is also a kind of utility. But instead of *just* providing an interface, it adapts an input object,
+providing a given interface, to provide another interface. An adapter can also be named, so that you can choose which
+adapter to use at a given time.
+
+PyAMS_utils provide another annotation, to help registering adapters without using ZCML files. An adapter can be a
+function which directly returns an object providing the requested interface, or an object which provides the interface.
+
+The first example is an adapter which adapts any persistent object to get it's associated transaction manager:
+
+.. code-block:: python
+
+    from persistent.interfaces import IPersistent
+    from transaction.interfaces import ITransactionManager
+    from ZODB.interfaces import IConnection
+
+    from pyams_utils.adapter import adapter_config
+
+    @adapter_config(context=IPersistent, provides=ITransactionManager)
+    def get_transaction_manager(obj):
+        conn = IConnection(obj)
+        try:
+            return conn.transaction_manager
+        except AttributeError:
+            return conn._txn_mgr
+
+This is another adapter which adapts any contained object to the *IPathElements* interface; this interface can be
+used to build index that you can use to find objects based on a parent object:
+
+.. code-block:: python
+
+    from pyams_utils.interfaces.traversing import IPathElements
+    from zope.intid.interfaces import IIntIds
+    from zope.location.interfaces import IContained
+
+    from pyams_utils.adapter import ContextAdapter
+    from pyams_utils.registry import query_utility
+    from pyramid.location import lineage
+
+    @adapter_config(context=IContained, provides=IPathElements)
+    class PathElementsAdapter(ContextAdapter):
+        """Contained object path elements adapter"""
+
+        @property
+        def parents(self):
+            intids = query_utility(IIntIds)
+            if intids is None:
+                return []
+            return [intids.register(parent) for parent in lineage(self.context)]
+
+An adapter can also be a multi-adapter, when several input objects are requested to provide a given interface. For
+example, many adapters require a context and a request, eventually a view, to provide another feature. This is how,
+for example, we define a custom *name* column in a security manager table displaying a list of plug-ins:
+
+.. code-block:: python
+
+    from pyams_zmi.layer import IAdminLayer
+    from z3c.table.interfaces import IColumn
+
+    from pyams_skin.table import I18nColumn
+    from z3c.table.column import GetAttrColumn
+
+    @adapter_config(name='name', context=(Interface, IAdminLayer, SecurityManagerPluginsTable), provides=IColumn)
+    class SecurityManagerPluginsNameColumn(I18nColumn, GetAttrColumn):
+        """Security manager plugins name column"""
+
+        _header = _("Name")
+        attrName = 'title'
+        weight = 10
+
+
+Registering vocabularies
+------------------------
+
+A **vocabulary** is a custom factory which can be used as source for several field types, like *choices* or *lists*.
+Vocabularies have to be registered in a custom registry, so PyAMS_utils provide another annotation to register them.
+This example is based on the *Timezone* component which allows you to select a timezone between a list of references:
+
+.. code-block:: python
+
+    import pytz
+    from pyams_utils.vocabulary import vocabulary_config
+    from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+
+    @vocabulary_config(name='PyAMS timezones')
+    class TimezonesVocabulary(SimpleVocabulary):
+        """Timezones vocabulary"""
+
+        def __init__(self, *args, **kw):
+            terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
+            super(TimezonesVocabulary, self).__init__(terms)
--- a/src/pyams_utils/adapter.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/adapter.py	Fri Nov 18 15:28:54 2016 +0100
@@ -10,6 +10,14 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+__doc__ = """Adapters management package
+
+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.
+"""
+
 __docformat__ = 'restructuredtext'
 
 
@@ -33,7 +41,7 @@
 
 
 class ContextRequestAdapter(object):
-    """Context + request adapter"""
+    """Context + request multi-adapter"""
 
     def __init__(self, context, request):
         self.context = context
@@ -41,7 +49,7 @@
 
 
 class ContextRequestViewAdapter(object):
-    """Context + request + view adapter"""
+    """Context + request + view multi-adapter"""
 
     def __init__(self, context, request, view):
         self.context = context
@@ -50,7 +58,14 @@
 
 
 class adapter_config(object):
-    """Function or class decorator to declare an adapter"""
+    """Function or class decorator to declare an adapter
+
+    Annotation parameters can be:
+
+    :param str name: (default=''), 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
+    """
 
     venusian = venusian
 
--- a/src/pyams_utils/attr.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/attr.py	Fri Nov 18 15:28:54 2016 +0100
@@ -26,7 +26,15 @@
 
 @adapter_config(name='attr', context=Interface, provides=ITraversable)
 class AttributeTraverser(ContextAdapter):
-    """++attr++ namespace traverser"""
+    """++attr++ namespace traverser
+
+    This custom traversing adapter can be used to access an object attribute directly from
+    an URL by using a path like this::
+
+    /path/to/object/++attr++name
+
+    Whare *name* is the name of the requested attribute
+    """
 
     def traverse(self, name, furtherpath=None):
         try:
--- a/src/pyams_utils/container.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/container.py	Fri Nov 18 15:28:54 2016 +0100
@@ -28,7 +28,10 @@
 
 
 class BTreeOrderedContainer(OrderedContainer):
-    """BTree based ordered container"""
+    """BTree based ordered container
+
+    This container maintain a manual order of it's contents
+    """
 
     def __init__(self):
         self._data = OOBTree()
@@ -37,9 +40,15 @@
 
 @adapter_config(context=IContained, provides=ISublocations)
 class ContainerSublocationsAdapter(ContextAdapter):
-    """Container sub-locations adapter"""
+    """Contained object sub-locations adapter
+
+    This adapter checks for custom ISublocations interface adapters which can
+    be defined by any component to get access to inner locations, defined for
+    example via annotations.
+    """
 
     def sublocations(self):
+        """See `zope.location.interfaces.ISublocations` interface"""
         context = self.context
         # Check for adapted sub-locations first...
         registry = get_current_registry()
@@ -61,6 +70,13 @@
     argument and must return a boolean result.
 
     All sub-objects of the root will also be searched recursively.
+
+    :param object root: the parent object from which search is started
+    :param callable condition: a callable object which may return true for a given
+        object to be selected
+    :param boolean ignore_root: if *True*, the root object will not be returned, even if it matches
+        the given condition
+    :return: an iterator for all root's sub-objects matching condition
     """
     if (not ignore_root) and condition(root):
         yield root
@@ -77,6 +93,10 @@
     """Find all objects in root that provide the specified interface
 
     All sub-objects of the root will also be searched recursively.
+
+    :param object root: object; the parent object from which search is started
+    :param Interface interface: interface; an interface that sub-objects should provide
+    :return: an iterator for all root's sub-objects that provide the given interface
     """
     for match in find_objects_matching(root, interface.providedBy):
         yield match
--- a/src/pyams_utils/context.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/context.py	Fri Nov 18 15:28:54 2016 +0100
@@ -23,9 +23,14 @@
 class ContextSelector(object):
     """Interface based context selector
 
-    This selector can be used on any subscriber to define
-    interfaces that the context must support for the event
-    to be applied
+    This selector can be used as a subscriber predicate to define
+    an interface that the context must support for the event to be applied::
+
+        from pyams_utils.interfaces.site import ISiteRoot
+
+        @subscriber(IObjectModifiedEvent, context_selector=ISiteRoot)
+        def siteroot_modified_event_handler(event):
+            '''This is an event handler for an ISiteRoot object modification event'''
     """
 
     def __init__(self, ifaces, config):
--- a/src/pyams_utils/data.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/data.py	Fri Nov 18 15:28:54 2016 +0100
@@ -10,6 +10,36 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+__doc__ = """Object data API module
+
+The *IObjectData* interface is a generic interface which can be used to assign custom data to nay 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:
+
+.. code-block:: python
+
+    def updateWidgets(self):
+        super(MyForm, self).updateWidgets()
+        widget = self.widgets['mywidget']
+        alsoProvides(widget, IObjectData)
+        widget.object_data = {'ams-colorpicker-position': 'top left'}
+
+You can then set an attribute in a TAL template like this:
+
+.. code-block:: html
+
+    <div tal:attributes="data-ams-data extension:object_data(widget)">...</div>
+
+After data initialization by **MyAMS.js**, the following code will be converted to:
+
+.. code-block:: html
+
+    <div data-ams-colorpicker-position="top left">...</div>
+"""
+
 __docformat__ = 'restructuredtext'
 
 
@@ -32,15 +62,23 @@
     """Object data JSON renderer"""
 
     def get_object_data(self):
+        """See `pyams_utils.interfaces.data.IObjectDataRenderer` interface"""
         data = IObjectData(self.context)
         return json.dumps(data.object_data) if data is not None else None
 
 
 @adapter_config(name='object_data', context=(Interface, Interface, Interface), provides=ITALESExtension)
 class ObjectDataExtension(ContextRequestViewAdapter):
-    """extension:object_data TALES extension"""
+    """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 `pyams_utils.interfaces.data.IObjectData` interface), like this::
+
+        <div tal:attributes="data-ams-data extension:object_data(context)">...</div>
+    """
 
     def render(self, context=None):
+        """See `pyams_utils.interfaces.tales.ITALESExtension` interface"""
         if context is None:
             context = self.context
         renderer = IObjectDataRenderer(context, None)
@@ -50,15 +88,29 @@
 
 @adapter_config(name='request_data', context=(Interface, IRequest, Interface), provides=ITALESExtension)
 class PyramidRequestDataExtension(ContextRequestViewAdapter):
-    """extension:request_data TALES extension for Pyramid request"""
+    """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.
+    For example::
+
+        <div tal:content="extension:request_data('my.annotation.key')">...</div>
+    """
 
     def render(self, params=None):
+        """See `pyams_utils.interfaces.tales.ITALESExtension` interface"""
         return self.request.annotations.get(params)
 
 
 @adapter_config(name='request_data', context=(Interface, IBrowserRequest, Interface), provides=ITALESExtension)
 class BrowserRequestDataExtension(ContextRequestViewAdapter):
-    """extension:request_data TALES extension for Zope browser request"""
+    """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.
+    For example::
+
+        <div tal:content="extension:request_data('my.annotation.key')">...</div>
+    """
 
     def render(self, params=None):
+        """See `pyams_utils.interfaces.tales.ITALESExtension` interface"""
         return self.request.annotations.get(params)
--- a/src/pyams_utils/date.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/date.py	Fri Nov 18 15:28:54 2016 +0100
@@ -31,10 +31,14 @@
     
     Dates are always assumed to be stored in GMT timezone
     
-    @param value: input date to convert to unicode
-    @type value: date or datetime
-    @return: input date converted to unicode
-    @rtype: unicode
+    :param date value: input date to convert to unicode
+    :return: unicode; input date converted to unicode
+
+    >>> from datetime import datetime
+    >>> from pyams_utils.date import unidate
+    >>> value = datetime(2016, 11, 15, 10, 13, 12)
+    >>> unidate(value)
+    '2016-11-15T10:13:12+00:00'
     """
     if value is not None:
         value = gmtime(value)
@@ -47,10 +51,12 @@
     
     Dates are always assumed to be stored in GMT timezone
     
-    @param value: unicode date to be parsed
-    @type value: unicode
-    @return: the specified value, converted to datetime
-    @rtype: datetime
+    :param str value: unicode date to be parsed
+    :return: datetime; the specified value, converted to datetime
+
+    >>> from pyams_utils.date import parse_date
+    >>> parse_date('2016-11-15T10:13:12+00:00')
+    datetime.datetime(2016, 11, 15, 10, 13, 12, tzinfo=<StaticTzInfo 'GMT'>)
     """
     if value is not None:
         return gmtime(parseDatetimetz(value))
@@ -60,10 +66,19 @@
 def date_to_datetime(value):
     """Get datetime value converted from a date or datetime object
     
-    @param value: a date or datetime value to convert
-    @type value: date or datetime
-    @return: input value converted to datetime
-    @rtype: datetime
+    :param date/datetime value: a date or datetime value to convert
+    :return: datetime; input value converted to datetime
+
+    >>> from datetime import date, datetime
+    >>> from pyams_utils.date import date_to_datetime
+    >>> value = date(2016, 11, 15)
+    >>> date_to_datetime(value)
+    datetime.datetime(2016, 11, 15, 0, 0)
+    >>> value = datetime(2016, 11, 15, 10, 13, 12)
+    >>> value
+    datetime.datetime(2016, 11, 15, 10, 13, 12)
+    >>> date_to_datetime(value) is value
+    True
     """
     if not value:
         return None
@@ -80,7 +95,21 @@
 
 
 def format_date(value, format=EXT_DATE_FORMAT, request=None):
-    """Format given date with the given format"""
+    """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 request: the request from which to extract localization info for translation
+    :return: str; input datetime converted to given format
+
+    >>> from datetime import datetime
+    >>> from pyams_utils.date import format_date, SH_DATE_FORMAT
+    >>> value = datetime(2016, 11, 15, 10, 13, 12)
+    >>> format_date(value)
+    'on 15/11/2016'
+    >>> format_date(value, SH_DATE_FORMAT)
+    '15/11/2016'
+    """
     if not value:
         return '--'
     if request is None:
@@ -90,12 +119,30 @@
 
 
 def format_datetime(value, format=EXT_DATETIME_FORMAT, request=None):
-    """Format given datetime with the given format"""
+    """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 request: request; the request from which to extract localization info for translation
+    :return: str; input datetime converted to given format
+
+    >>> from datetime import datetime
+    >>> from pyams_utils.date import format_datetime, SH_DATETIME_FORMAT
+    >>> value = datetime(2016, 11, 15, 10, 13, 12)
+    >>> format_datetime(value)
+    'on 15/11/2016 at 10:13'
+    >>> format_datetime(value, SH_DATETIME_FORMAT)
+    '15/11/2016 - 10:13'
+    """
     return format_date(value, format, request)
 
 
 def get_age(value, request=None):
-    """Get 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
+    """
     if request is None:
         request = check_request()
     translate = request.localizer.translate
@@ -122,7 +169,12 @@
 
 
 def get_duration(v1, v2=None, request=None):
-    """Get delta as string between two dates
+    """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 request: the request from which to extract localization infos
+    :return: str; approximate delta between the two input dates
 
     >>> from datetime import datetime
     >>> from pyams_utils.date import get_duration
@@ -134,6 +186,7 @@
     '10 months'
 
     Dates order is not important:
+
     >>> get_duration(date2, date1, request)
     '10 months'
     >>> date2 = datetime(2014, 11, 10)
@@ -144,6 +197,7 @@
     '6 days'
 
     For durations lower than 2 days, duration also display hours:
+
     >>> date1 = datetime(2015, 1, 1)
     >>> date2 = datetime(2015, 1, 2, 15, 10, 0)
     >>> get_duration(date1, date2, request)
--- a/src/pyams_utils/encoding.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/encoding.py	Fri Nov 18 15:28:54 2016 +0100
@@ -126,6 +126,7 @@
 
 @vocabulary_config(name='PyAMS encodings')
 class EncodingsVocabulary(SimpleVocabulary):
+    """A vocabulary containing a set of registered encodings"""
 
     def __init__(self, terms, *interfaces):
         request = check_request()
@@ -141,7 +142,7 @@
 
 @implementer(IEncodingField)
 class EncodingField(Choice):
-    """Encoding field"""
+    """Encoding schema field"""
 
     def __init__(self, vocabulary='PyAMS encodings', **kw):
         if 'values' in kw:
--- a/src/pyams_utils/html.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/html.py	Fri Nov 18 15:28:54 2016 +0100
@@ -111,10 +111,10 @@
     'This is a HTML text part.\\n'
 
     HTML parser should handle entities correctly:
+
     >>> 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 < o > entity.\\n\\n'
-
     """
     if value is None:
         return ''
--- a/src/pyams_utils/interfaces/__init__.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/interfaces/__init__.py	Fri Nov 18 15:28:54 2016 +0100
@@ -24,40 +24,39 @@
 # Custom string constants
 #
 
-# Custom permission which is never granted to any user
 FORBIDDEN_PERMISSION = 'system.forbidden'
-
-# public permission is granted to every principal
-PUBLIC_PERMISSION = 'public'
+'''Custom permission which is never granted to any user'''
 
-# view permission is a custom permission used to view contents
-VIEW_PERMISSION = 'view'
+PUBLIC_PERMISSION = 'public'
+'''Public permission is granted to every principal'''
 
-# permission used to manage basic informations
-# this permission is generally not used by custom contents
+VIEW_PERMISSION = 'view'
+'''View permission is a custom permission used to view contents'''
+
 MANAGE_PERMISSION = 'manage'
+'''Permission used to manage basic informations this permission is generally not used by custom contents'''
 
-# permission used to access management screens
 VIEW_SYSTEM_PERMISSION = 'system.view'
+'''Permission used to access management screens'''
 
-# permission used to manage system settings
 MANAGE_SYSTEM_PERMISSION = 'system.manage'
+'''Permission used to manage system settings'''
 
-# permission used to manage security settings
 MANAGE_SECURITY_PERMISSION = 'security.manage'
+'''Permission used to manage security settings'''
 
-# permission used to manage roles
 MANAGE_ROLES_PERMISSION = 'security.manage_roles'
+'''Permission used to manage roles'''
 
 
-# ZODB application name settings key
 PYAMS_APPLICATION_SETTINGS_KEY = 'pyams.application_name'
+'''ZODB application name settings key'''
 
-# ZODB default application name
 PYAMS_APPLICATION_DEFAULT_NAME = 'application'
+'''ZODB default application name'''
 
-# Settings key to define site root factory
 PYAMS_APPLICATION_FACTORY_KEY = 'pyams.application_factory'
+'''Settings key to define site root factory'''
 
 
 class MissingRequestError(Exception):
--- a/src/pyams_utils/intids.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/intids.py	Fri Nov 18 15:28:54 2016 +0100
@@ -24,7 +24,7 @@
 from zope.keyreference.interfaces import IKeyReference, NotYet
 
 # import packages
-from pyams_utils.adapter import adapter_config
+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
@@ -33,17 +33,16 @@
 
 
 @adapter_config(context=IPersistent, provides=IUniqueID)
-class UniqueIdAdapter(object):
+class UniqueIdAdapter(ContextAdapter):
     """Object unique ID adapter
 
-    This adapter is based on a registered IIntIds utility
+    This adapter is based on a registered IIntIds utility to get a unique ID
+    for any persistent object.
     """
 
-    def __init__(self, context):
-        self.context = context
-
     @property
     def oid(self):
+        """Get context ID in hexadecimal form"""
         intids = query_utility(IIntIds)
         if intids is not None:
             return hex(intids.queryId(self.context))[2:]
@@ -51,7 +50,11 @@
 
 @subscriber(IObjectAddedEvent, context_selector=IPersistent)
 def handle_added_object(event):
-    """Notify IntId utility for added objects"""
+    """Notify IntId utility for added objects
+
+    This subscriber is used for all persistent objects to be registered
+    in all locally registered IIntIds utilities.
+    """
     utilities = tuple(get_all_utilities_registered_for(IIntIds))
     if utilities:
         # assert that there are any utilities
@@ -71,7 +74,11 @@
 
 @subscriber(IObjectRemovedEvent, context_selector=IPersistent)
 def handle_removed_object(event):
-    """Notify IntId utility for removed objects"""
+    """Notify IntId utility for removed objects
+
+    This subscriber is used for all persistent objects to be unregistered
+    from all locally registered IIntIds utilities.
+    """
     registry = get_current_registry()
     locations = ISublocations(event.object, None)
     if locations is not None:
@@ -93,4 +100,7 @@
 
 @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
+    """
     intIdEventNotify(event)
--- a/src/pyams_utils/list.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/list.py	Fri Nov 18 15:28:54 2016 +0100
@@ -23,7 +23,11 @@
 def unique(seq, idfun=None):
     """Extract unique values from list, preserving order
 
-    Original list is not modified.
+    :param list seq: input list
+    :param callable idfun: 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.
 
     >>> from pyams_utils.list import unique
     >>> mylist = [1, 2, 3, 2, 1]
@@ -35,6 +39,7 @@
     [3, 2, 1, 4]
 
     You can also set an 'id' function applied on each element:
+
     >>> mylist = [1, 2, 3, '2', 4]
     >>> unique(mylist, idfun=str)
     [1, 2, 3, 4]
Binary file src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo has changed
--- a/src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po	Fri Nov 18 15:28:54 2016 +0100
@@ -4,7 +4,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-04-01 09:37+0200\n"
+"POT-Creation-Date: 2016-05-10 16:09+0200\n"
 "PO-Revision-Date: 2015-01-18 01:01+0100\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -31,78 +31,78 @@
 msgid "on %d/%m/%Y at %H:%M"
 msgstr "le %d/%m/%Y à %H:%M"
 
-#: src/pyams_utils/date.py:108
+#: src/pyams_utils/date.py:110
 #, c-format
 msgid "%d months ago"
 msgstr "Il y a %d mois"
 
-#: src/pyams_utils/date.py:176
+#: src/pyams_utils/date.py:178
 #, c-format
 msgid "%d months"
 msgstr "%d mois"
 
-#: src/pyams_utils/date.py:110
+#: src/pyams_utils/date.py:112
 #, c-format
 msgid "%d weeks ago"
 msgstr "Il y a %d semaines"
 
-#: src/pyams_utils/date.py:178
+#: src/pyams_utils/date.py:180
 #, c-format
 msgid "%d weeks"
 msgstr "%d semaines"
 
-#: src/pyams_utils/date.py:112
+#: src/pyams_utils/date.py:114
 #, c-format
 msgid "%d days ago"
 msgstr "Il y a %d jours"
 
-#: src/pyams_utils/date.py:114
+#: src/pyams_utils/date.py:116
 msgid "the day before yesterday"
 msgstr "avant-hier"
 
-#: src/pyams_utils/date.py:180
+#: src/pyams_utils/date.py:182
 #, c-format
 msgid "%d days"
 msgstr "%d jours"
 
-#: src/pyams_utils/date.py:116
+#: src/pyams_utils/date.py:118
 msgid "yesterday"
 msgstr "hier"
 
-#: src/pyams_utils/date.py:185
+#: src/pyams_utils/date.py:187
 msgid "24 hours"
 msgstr "24 heures"
 
-#: src/pyams_utils/date.py:187
+#: src/pyams_utils/date.py:189
 #, c-format
 msgid "%d day and %d hours"
 msgstr "%d jours et %d heures"
 
-#: src/pyams_utils/date.py:190
+#: src/pyams_utils/date.py:192
 #, c-format
 msgid "%d hours"
 msgstr "%d heures"
 
-#: src/pyams_utils/date.py:120
+#: src/pyams_utils/date.py:122
 #, c-format
 msgid "%d hours ago"
 msgstr "Il y a %d heures"
 
-#: src/pyams_utils/date.py:124
+#: src/pyams_utils/date.py:126
 msgid "less than 5 minutes ago"
 msgstr "Il y a moins de 5 minutes"
 
-#: src/pyams_utils/date.py:194
+#: src/pyams_utils/date.py:196
 #, c-format
 msgid "%d minutes"
 msgstr "%d minutes"
 
-#: src/pyams_utils/date.py:196
+#: src/pyams_utils/date.py:198
 #, c-format
 msgid "%d seconds"
 msgstr "%d secondes"
 
-#: src/pyams_utils/date.py:122
+#: src/pyams_utils/date.py:124
 #, c-format
 msgid "%d minutes ago"
 msgstr "Il y a %d minutes"
@@ -493,11 +493,11 @@
 msgid "all languages (utf_8_sig)"
 msgstr "toutes les langues (utf-8-sig)"
 
-#: src/pyams_utils/schema.py:69
+#: src/pyams_utils/schema.py:114
 msgid "Color length must be 3 or 6 characters"
 msgstr "La longueur d'une couleur doit être de 3 ou 6 caractères"
 
-#: src/pyams_utils/schema.py:72
+#: src/pyams_utils/schema.py:117
 msgid ""
 "Color value must contain only valid hexadecimal color codes (numbers or "
 "letters between 'A' end 'F')"
@@ -509,39 +509,64 @@
 msgid "The entered value is not a valid decimal literal."
 msgstr "La valeur saisie n'est pas une valeur décimale correcte."
 
-#: src/pyams_utils/zmi/zeo.py:49
+#: src/pyams_utils/zmi/zeo.py:68
 msgid "Add ZEO connection..."
 msgstr "Ajouter une connexion ZEO..."
 
-#: src/pyams_utils/zmi/zeo.py:59
+#: src/pyams_utils/zmi/zeo.py:78
 msgid "Utilities"
 msgstr "Utilitaires"
 
-#: src/pyams_utils/zmi/zeo.py:60
+#: src/pyams_utils/zmi/zeo.py:79
 msgid "Add ZEO connection"
 msgstr "Ajout d'une connexion ZEO"
 
-#: src/pyams_utils/zmi/zeo.py:105
+#: src/pyams_utils/zmi/zeo.py:125
 msgid "Update ZEO connection properties"
 msgstr "Propriétés d'une connexion ZEO"
 
-#: src/pyams_utils/zmi/zeo.py:84
+#: src/pyams_utils/zmi/zeo.py:153
+msgid "Test ZEO connection..."
+msgstr "Tester la connexion ZEO..."
+
+#: src/pyams_utils/zmi/zeo.py:176
+msgid "Test ZEO database connection"
+msgstr "Test de la connexion ZEO"
+
+#: src/pyams_utils/zmi/zeo.py:163
+msgid "Close"
+msgstr "Fermer"
+
+#: src/pyams_utils/zmi/zeo.py:164
+msgid "Test connection"
+msgstr "Tester la connexion"
+
+#: src/pyams_utils/zmi/zeo.py:104
 msgid "Specified connection name is already used!"
 msgstr "Ce nom de connexion est déjà utilisé !"
 
-#: src/pyams_utils/zmi/zeo.py:87
+#: src/pyams_utils/zmi/zeo.py:107
 msgid "A ZEO connection is already registered with this name!"
 msgstr "Une connexion ZEO est déjà enregistrées avec ce nom !"
 
-#: src/pyams_utils/zmi/zeo.py:103
+#: src/pyams_utils/zmi/zeo.py:60
+#, python-format
+msgid "ZEO: {0}"
+msgstr "ZEO : {0}"
+
+#: src/pyams_utils/zmi/zeo.py:123
 #, python-format
 msgid "ZEO connection: {0}"
 msgstr "Connexion ZEO : {0}"
 
-#: src/pyams_utils/zmi/timezone.py:37
+#: src/pyams_utils/zmi/timezone.py:38
 msgid "Update server timezone properties"
 msgstr "Fuseau horaire du serveur"
 
+#: src/pyams_utils/zmi/intids.py:46
+msgid "Display indexer properties"
+msgstr "Propriétés de l'indexeur"
+
 #: src/pyams_utils/interfaces/text.py:34
 msgid "Renderer name"
 msgstr "Nom de l'outil de rendu"
@@ -635,6 +660,3 @@
 #: src/pyams_utils/interfaces/timezone.py:48
 msgid "Default server timezone"
 msgstr "Fuseau horaire par défaut"
-
-#~ msgid "Test"
-#~ msgstr "test"
--- a/src/pyams_utils/locales/pyams_utils.pot	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/locales/pyams_utils.pot	Fri Nov 18 15:28:54 2016 +0100
@@ -1,12 +1,12 @@
 # 
 # SOME DESCRIPTIVE TITLE
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-04-01 09:37+0200\n"
+"POT-Creation-Date: 2016-05-10 16:09+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,78 +32,78 @@
 msgid "on %d/%m/%Y at %H:%M"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:108
+#: ./src/pyams_utils/date.py:110
 #, c-format
 msgid "%d months ago"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:176
+#: ./src/pyams_utils/date.py:178
 #, c-format
 msgid "%d months"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:110
+#: ./src/pyams_utils/date.py:112
 #, c-format
 msgid "%d weeks ago"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:178
+#: ./src/pyams_utils/date.py:180
 #, c-format
 msgid "%d weeks"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:112
+#: ./src/pyams_utils/date.py:114
 #, c-format
 msgid "%d days ago"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:114
+#: ./src/pyams_utils/date.py:116
 msgid "the day before yesterday"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:180
+#: ./src/pyams_utils/date.py:182
 #, c-format
 msgid "%d days"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:116
+#: ./src/pyams_utils/date.py:118
 msgid "yesterday"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:185
+#: ./src/pyams_utils/date.py:187
 msgid "24 hours"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:187
+#: ./src/pyams_utils/date.py:189
 #, c-format
 msgid "%d day and %d hours"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:190
+#: ./src/pyams_utils/date.py:192
 #, c-format
 msgid "%d hours"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:120
+#: ./src/pyams_utils/date.py:122
 #, c-format
 msgid "%d hours ago"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:124
+#: ./src/pyams_utils/date.py:126
 msgid "less than 5 minutes ago"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:194
+#: ./src/pyams_utils/date.py:196
 #, c-format
 msgid "%d minutes"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:196
+#: ./src/pyams_utils/date.py:198
 #, c-format
 msgid "%d seconds"
 msgstr ""
 
-#: ./src/pyams_utils/date.py:122
+#: ./src/pyams_utils/date.py:124
 #, c-format
 msgid "%d minutes ago"
 msgstr ""
@@ -493,11 +493,11 @@
 msgid "all languages (utf_8_sig)"
 msgstr ""
 
-#: ./src/pyams_utils/schema.py:69
+#: ./src/pyams_utils/schema.py:114
 msgid "Color length must be 3 or 6 characters"
 msgstr ""
 
-#: ./src/pyams_utils/schema.py:72
+#: ./src/pyams_utils/schema.py:117
 msgid ""
 "Color value must contain only valid hexadecimal color codes (numbers or "
 "letters between 'A' end 'F')"
@@ -507,39 +507,64 @@
 msgid "The entered value is not a valid decimal literal."
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:49
+#: ./src/pyams_utils/zmi/zeo.py:68
 msgid "Add ZEO connection..."
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:59
+#: ./src/pyams_utils/zmi/zeo.py:78
 msgid "Utilities"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:60
+#: ./src/pyams_utils/zmi/zeo.py:79
 msgid "Add ZEO connection"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:105
+#: ./src/pyams_utils/zmi/zeo.py:125
 msgid "Update ZEO connection properties"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:84
+#: ./src/pyams_utils/zmi/zeo.py:153
+msgid "Test ZEO connection..."
+msgstr ""
+
+#: ./src/pyams_utils/zmi/zeo.py:176
+msgid "Test ZEO database connection"
+msgstr ""
+
+#: ./src/pyams_utils/zmi/zeo.py:163
+msgid "Close"
+msgstr ""
+
+#: ./src/pyams_utils/zmi/zeo.py:164
+msgid "Test connection"
+msgstr ""
+
+#: ./src/pyams_utils/zmi/zeo.py:104
 msgid "Specified connection name is already used!"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:87
+#: ./src/pyams_utils/zmi/zeo.py:107
 msgid "A ZEO connection is already registered with this name!"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/zeo.py:103
+#: ./src/pyams_utils/zmi/zeo.py:60
+#, python-format
+msgid "ZEO: {0}"
+msgstr ""
+
+#: ./src/pyams_utils/zmi/zeo.py:123
 #, python-format
 msgid "ZEO connection: {0}"
 msgstr ""
 
-#: ./src/pyams_utils/zmi/timezone.py:37
+#: ./src/pyams_utils/zmi/timezone.py:38
 msgid "Update server timezone properties"
 msgstr ""
 
+#: ./src/pyams_utils/zmi/intids.py:46
+msgid "Display indexer properties"
+msgstr ""
+
 #: ./src/pyams_utils/interfaces/text.py:34
 msgid "Renderer name"
 msgstr ""
--- a/src/pyams_utils/lock.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/lock.py	Fri Nov 18 15:28:54 2016 +0100
@@ -41,7 +41,17 @@
 
 
 class CacheLock(object):
-    """Beaker based lock"""
+    """Beaker based lock
+
+    This lock can be used when you need to get a lot across several processes or even computers.
+    The lock relies on a shared value stored into a shared Beaker cache.
+
+    :param str name: name of the lock to use as shared key
+    :param boolean wait: if *False*, a *LockException* is raised if lock can't be taken; otherwise,
+        application waits until lock is released
+
+    Lock can be used as a context manager.
+    """
 
     def __init__(self, name, wait=True):
         self.key = 'PyAMS_lock::{0}'.format(name)
@@ -69,7 +79,14 @@
 
 
 def locked(name, wait=True):
-    """Locked function decorator"""
+    """Locked function decorator
+
+    Can be used with any function or method which requires a global shared lock.
+
+    :param str name: name of the lock to use as shared key
+    :param boolean wait: if *False*, a *LockException* is raised if lock can't be taken; otherwise,
+        application waits until lock is released
+    """
 
     def lock_decorator(func):
         def wrapper(*args, **kwargs):
--- a/src/pyams_utils/progress.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/progress.py	Fri Nov 18 15:28:54 2016 +0100
@@ -117,7 +117,10 @@
 
 @view_config(name='get_progress_status.json', renderer='JSON', xhr=True)
 def get_progress_status_view(request):
-    """Get progress status"""
+    """Get progress status of a given task
+
+    Each submitted task is identified by an ID defined when the task is created
+    """
     if 'progress_id' not in request.params:
         raise HTTPBadRequest("Missing argument")
     return get_progress_status(request.params['progress_id'])
--- a/src/pyams_utils/property.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/property.py	Fri Nov 18 15:28:54 2016 +0100
@@ -36,8 +36,7 @@
 
 
 class cached(object):
-    """Custom property decorator to define a property or function
-    which is calculated only once
+    """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
     """
@@ -58,8 +57,9 @@
 
 
 class cached_property(object):
-    """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
+    """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
     memory leakage.
     """
     def __init__(self, fget, doc=None):
@@ -83,6 +83,10 @@
 
     If not 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: session's 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):
@@ -111,8 +115,10 @@
 
     If no request is currently running, a new one is created.
 
-    @app: application identifier used to prefix session keys
-    @key: session's value key; if None, the key will be the method's object
+    :param str app: application identifier used to prefix session keys
+    :param str key: session's value key; if *None*, the key will be the method's object; if *key* is a callable
+        object, il 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 session_decorator(func):
--- a/src/pyams_utils/protocol/http.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/protocol/http.py	Fri Nov 18 15:28:54 2016 +0100
@@ -23,7 +23,7 @@
 
 
 class HTTPClient(object):
-    """HTTP client"""
+    """HTTP client with proxy support"""
 
     def __init__(self, method, protocol, servername, url, params={}, credentials=(),
                  proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
@@ -46,7 +46,7 @@
 
     def get_response(self):
         """Common HTTP request"""
-        if self.proxy:
+        if self.proxy and (len(self.proxy) == 2):
             proxy_info = httplib2.ProxyInfo(httplib2.socks.PROXY_TYPE_HTTP,
                                             proxy_host=self.proxy[0],
                                             proxy_port=self.proxy[1],
--- a/src/pyams_utils/registry.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/registry.py	Fri Nov 18 15:28:54 2016 +0100
@@ -10,6 +10,17 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+__doc__ = """Local registry management package
+
+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.
+
+See :ref:`zca` to get a brief introduction about using a local registry with PyAMS packages.
+"""
+
 __docformat__ = 'restructuredtext'
 
 
@@ -58,17 +69,28 @@
 
 @subscriber(INewRequest)
 def handle_new_request(event):
+    """New request event subscriber
+
+    Is used to initialize local registry to None for any new request
+    """
     set_local_registry(None)
 
 
 @subscriber(IBeforeTraverseEvent, context_selector=ISite)
 def handle_site_before_traverse(event):
-    """Define local registry when an object implementing ISite is traversed"""
+    """Before traverse event subscriber
+
+    Define site's local registry when an object implementing ISite is traversed
+    """
     set_local_registry(event.object.getSiteManager())
 
 
 def get_registries():
-    """Get iterator of components registries"""
+    """Iterator on components registries
+
+    Returns an iterator on current local registry (if any) and registries associated
+    in current thread stack.
+    """
     registry = local_registry.get_registry()
     if registry is not None:
         yield registry
@@ -79,14 +101,26 @@
 
 
 def registered_utilities():
-    """Get utilities registrations as generator"""
+    """Get utilities registrations as generator
+
+    Iterates over utilities defined in all registries, starting with local ones.
+    """
     for registry in get_registries():
         for utility in registry.registeredUtilities():
             yield utility
 
 
 def query_utility(provided, name='', default=None):
-    """Query utility registered with given interface"""
+    """Query utility registered with given interface
+
+    Do a registry lookup for given utility into local registry first, then on each registry
+    associated with current thread stack.
+
+    :param Interface provided: the requested interface
+    :param str name: name of the requested utility
+    :param object default: the default object returned if the requested utility can't be found
+    :return: object; the requested object, or *default* if it can't be found
+    """
     try:
         for registry in get_registries():
             utility = registry.queryUtility(provided, name, default)
@@ -98,7 +132,16 @@
 
 
 def get_utility(provided, name=''):
-    """Get utility registered with given interface"""
+    """Get utility registered with given interface
+
+    Do a registry lookup for given utility into local registry first, then on each registry
+    associated with current thread stack.
+
+    :param Interface provided: the requested interface
+    :param str name: name of the requested utility
+    :return: object; the requested object. A *ComponentLookupError* is raised if the utility
+        can't be found.
+    """
     for registry in get_registries():
         utility = registry.queryUtility(provided, name)
         if utility is not None:
@@ -107,14 +150,22 @@
 
 
 def get_utilities_for(interface):
-    """Get utilities registered with given interface as (name, util)"""
+    """Get utilities registered with given interface as (name, util) tuples iterator
+
+    Do a registry lookup for matching utilities into local registry first, then on each registry
+    associated with current thread stack.
+    """
     for registry in get_registries():
         for utility in registry.getUtilitiesFor(interface):
             yield utility
 
 
 def get_all_utilities_registered_for(interface):
-    """Get list of registered utilities for given interface"""
+    """Get list of registered utilities for given interface
+
+    Do a registry lookup for matching utilities into local registry first, then on each registry
+    associated with current thread stack.
+    """
     result = []
     for registry in get_registries():
         for utilities in registry.getAllUtilitiesRegisteredFor(interface):
@@ -123,7 +174,13 @@
 
 
 class utility_config(object):
-    """Function or class decorator to declare a utility"""
+    """Function or class decorator to register a utility in the global registry
+
+    :param str name: default=''; name under which the utility is registered
+    :param Interface provides: the interface for which the utility is registered
+
+    Please note that a single utility can be registered several times (using several annotations).
+    """
 
     venusian = venusian
 
--- a/src/pyams_utils/request.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/request.py	Fri Nov 18 15:28:54 2016 +0100
@@ -28,7 +28,8 @@
 def get_request(raise_exception=True):
     """Get current request
 
-    Raises a NoInteraction exception if there is no active request"""
+    Raises a NoInteraction exception if there is no active request.
+    """
     request = get_current_request()
     if (request is None) and raise_exception:
         raise MissingRequestError("No request")
@@ -56,13 +57,19 @@
 
 
 def get_annotations(request):
-    """Define 'annotations' request property"""
+    """Define 'annotations' request property
+
+    This function is automatically defined as a custom request method on package include.
+    """
     alsoProvides(request, IAttributeAnnotatable)
     return IAnnotations(request)
 
 
 def get_debug(request):
-    """Define 'debug' request property"""
+    """Define 'debug' request property
+
+    This function is automatically defined as a custom request method on package include.
+    """
     class Debug():
         def __init__(self):
             self.showTAL = False
@@ -71,12 +78,23 @@
 
 
 def get_request_data(request, key, default=None):
-    """Get data associated with request"""
+    """Get data associated with request
+
+    :param request: the request containing requested data
+    :param str key: request data annotation key
+    :param object default: the default value when data is missing
+    :return: the requested value, or *default*
+    """
     annotations = request.annotations
     return annotations.get(key, default)
 
 
 def set_request_data(request, key, value):
-    """Associate data with request"""
+    """Associate data with request
+
+    :param request: the request in which to set data
+    :param str key: request data annotation key
+    :param object value: the value to be set in request annotation
+    """
     annotations = request.annotations
     annotations[key] = value
--- a/src/pyams_utils/session.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/session.py	Fri Nov 18 15:28:54 2016 +0100
@@ -21,12 +21,43 @@
 
 
 def get_session_data(request, app, key, default=None):
-    """Get data associated with a given session"""
+    """Get data associated with current user session
+
+    PyAMS session management is based on :py:mod:`Beaker` package session management.
+
+    :param request: the request from which session is extracted
+    :param str app: application name
+    :param str key: session data key for given application
+    :param default: object; requested session data, or *default* if it can't be found
+
+    .. code-block:: python
+
+        APPLICATION_KEY = 'MyApp'
+        SESSION_KEY = 'MyFunction'
+
+        def my_function(request):
+            return get_session_data(request, APPLICATION_KEY, SESSION_KEY)
+    """
     session = request.session
     return session.get('{0}::{1}'.format(app, key), default)
 
 
 def set_session_data(request, app, key, value):
-    """Set data associated to a given session"""
+    """Associate data with current user session
+
+    :param request: the request from which session is extracted
+    :param str app: application name
+    :param str key: session data key for given application
+    :param object value: any object that can be pickled can be stored into user session
+
+    .. code-block:: python
+
+        APPLICATION_KEY = 'MyApp'
+        SESSION_KEY = 'MyFunction'
+
+        def my_function(request):
+            value = {'key1': 'value1', 'key2': 'value2'}
+            set_session_data(request, APPLICATION_KEY, SESSION_KEY, value)
+    """
     session = request.session
     session['{0}::{1}'.format(app, key)] = value
--- a/src/pyams_utils/site.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/site.py	Fri Nov 18 15:28:54 2016 +0100
@@ -42,7 +42,13 @@
 
 @implementer(ISiteRoot, IStaticConfigurationManager, IConfigurationManager)
 class BaseSiteRoot(Folder, SiteManagerContainer):
-    """Default site root"""
+    """Default site root
+
+    A site root can be used as base application root in your ZODB.
+    It's also site root responsibility to manage your local site manager.
+
+    BaseSiteRoot defines a basic ACL which gives all permissions to system administrator.
+    """
 
     __acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS)]
 
@@ -51,7 +57,10 @@
 
 @adapter_config(name='etc', context=ISiteRoot, provides=ITraversable)
 class SiteRootEtcTraverser(ContextAdapter):
-    """Site root ++etc++ namespace traverser"""
+    """Site root ++etc++ namespace traverser
+
+    Gives access to local site manager from */++etc++site* URL
+    """
 
     def traverse(self, name, furtherpath=None):
         if name == 'site':
@@ -61,11 +70,16 @@
 
 @implementer(INewLocalSiteCreatedEvent)
 class NewLocalSiteCreatedEvent(ObjectEvent):
-    """New local site created event"""
+    """New local site creation event"""
 
 
 def site_factory(request):
-    """Build a new site including registered utilities"""
+    """Application site factory
+
+    On application startup, this factory checks configuration to get application name and
+    load it from the ZODB; if the application can't be found, configuration is scanned to
+    get application factory, create a new one and create a local site manager.
+    """
     conn = get_connection(request)
     root = conn.root()
     application_key = request.registry.settings.get(PYAMS_APPLICATION_SETTINGS_KEY,
@@ -103,10 +117,11 @@
 def site_upgrade(request):
     """Upgrade site when needed
 
-    This function is executed by pyams_upgrade console script.
-    Site generations are registered as named utilities providing
-    ISiteGenerations interface.
-    Current site generations are stored into annotations.
+    This function is executed by *pyams_upgrade* console script.
+    Site generations are registered named utilities providing
+    :py:class:`ISiteGenerations <pyams_utils.interfaces.site.ISiteGenerations>` interface.
+
+    Current site generations are stored into annotations for each generation adapter.
     """
     application = site_factory(request)
     if application is not None:
@@ -134,11 +149,14 @@
 def check_required_utilities(site, utilities):
     """Utility function to check for required utilities
 
-    utilities argument is a tuple made of:
-    - the utility interface
-    - the utility name
-    - the utility factory
-    - the default name when creating the utility
+    :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:
+
+    .. code-block:: python
+
+        REQUIRED_UTILITIES = ((ISecurityManager, '', SecurityManager, 'Security manager'),
+                              (IPrincipalAnnotationUtility, '', PrincipalAnnotationUtility, 'User profiles'))
     """
     registry = get_current_registry()
     for interface, name, factory, default_id in utilities:
--- a/src/pyams_utils/size.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/size.py	Fri Nov 18 15:28:54 2016 +0100
@@ -29,11 +29,21 @@
     """Convert given bytes value in human readable format
 
     >>> from pyramid.testing import DummyRequest
+    >>> request = DummyRequest(params={'_LOCALE_': 'en'})
+    >>> request.locale_name
+    'en'
+    >>> from pyams_utils.size import get_human_size
+    >>> get_human_size(256, request)
+    '256 bytes'
+    >>> get_human_size(3678, request)
+    '3.6 Kb'
+    >>> get_human_size(6785342, request)
+    '6.47 Mb'
+    >>> get_human_size(3674815342, request)
+    '3.422 Gb'
     >>> request = DummyRequest(params={'_LOCALE_': 'fr'})
     >>> request.locale_name
     'fr'
-
-    >>> from pyams_utils.size import get_human_size
     >>> get_human_size(256, request)
     '256 bytes'
     >>> get_human_size(3678, request)
--- a/src/pyams_utils/tales.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/tales.py	Fri Nov 18 15:28:54 2016 +0100
@@ -27,7 +27,7 @@
 
 
 class ContextExprMixin(object):
-    """Mixin-class for expression compilers."""
+    """Mixin-class for expression compilers"""
 
     transform = None
 
@@ -44,22 +44,92 @@
 ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
 
 
-def get_value(econtext, arg):
-    """Extract argument value from context"""
-    arg = arg.strip()
-    if arg.startswith('"') or arg.startswith("'"):
-        # may be a quoted string...
-        return arg[1:-1]
-    args = arg.split('.')
-    result = econtext.get(args.pop(0))
-    for arg in args:
-        result = getattr(result, arg)
-    return result
+def render_extension(econtext, name):
+    """TALES extension renderer
+
+    This renderer can be used to render an *extension:* TALES expression.
+    When this expression is encountered, the renderer is looking for an
+    :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+    multi-adapter for the current *context*, *request* and *view*, for the current
+    *context* and *request*, or only for the current *context*, in this order.
+    If an adapter is found, the renderer call it's :py:func:`render` method with
+    the expression parameters as input parameters.
+
+    For example, the *metas* extension is an *ITALESExtension* adapter defined into
+    :py:mod:`pyams_skin.metas` module which can be used to include all required headers in
+    a page template. Extension is used like this in the page layout template:
+
+    .. code-block:: html
+
+        <tal:var replace="structure extension:metas" />
+
+    This extension is defined like this:
+
+    .. code-block:: python
+
+        from pyams_skin.interfaces.metas import IHTMLContentMetas
+        from pyams_utils.interfaces.tales import ITALESExtension
+        from pyramid.interfaces import IRequest
+
+        from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+
+        @adapter_config(name='metas', context=(Interface, IRequest, Interface), provides=ITALESExtension)
+        class MetasTalesExtension(ContextRequestViewAdapter):
+            '''extension:metas TALES extension'''
+
+            def render(self, context=None):
+                if context is None:
+                    context = self.context
+                result = []
+                for name, adapter in sorted(self.request.registry.getAdapters((context, self.request, self.view),
+                                                                              IHTMLContentMetas),
+                                            key=lambda x: getattr(x[1], 'order', 9999)):
+                    result.extend([meta.render() for meta in adapter.get_metas()])
+                return '\n\t'.join(result)
 
+    Some TALES extension can require or accept arguments. For example, the *absolute_url* extension can accept
+    a context and a view name:
 
-def render_extension(econtext, name):
+    .. code-block:: html
+
+        <tal:var define="logo config.logo">
+            <img tal:attributes="src extension:absolute_url(logo, '++thumb++200x36.png');" />
+        </tal:var>
+
+    The extension is defined like this:
+
+    .. code-block:: python
+
+        from persistent.interfaces import IPersistent
+        from pyams_utils.interfaces.tales import ITALESExtension
+
+        from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+        from pyramid.url import resource_url
+        from zope.interface import Interface
+
+        @adapter_config(name='absolute_url', context=(IPersistent, Interface, Interface), provides=ITALESExtension)
+        class AbsoluteUrlTalesExtension(ContextRequestViewAdapter):
+            '''extension:absolute_url(context, view_name) TALES extension'''
+
+            def render(self, context=None, view_name=None):
+                if context is None:
+                    context = self.context
+                return resource_url(context, self.request, view_name)
+    """
+
+    def get_value(econtext, arg):
+        """Extract argument value from context"""
+        arg = arg.strip()
+        if arg.startswith('"') or arg.startswith("'"):
+            # may be a quoted string...
+            return arg[1:-1]
+        args = arg.split('.')
+        result = econtext.get(args.pop(0))
+        for arg in args:
+            result = getattr(result, arg)
+        return result
+
     name = name.strip()
-
     context = econtext.get('context')
     request = econtext.get('request')
     view = econtext.get('view')
--- a/src/pyams_utils/text.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/text.py	Fri Nov 18 15:28:54 2016 +0100
@@ -33,11 +33,11 @@
 def get_text_start(text, length, max=0):
     """Get first words of given text with maximum given length
     
-    If @max is specified, text is shortened only if remaining text is longer than @max
+    If *max* is specified, text is shortened only if remaining text is longer this value
     
-    @param text: initial text
-    @param length: maximum length of resulting text
-    @param max: if > 0, @text is shortened only if remaining text is longer than max
+    :param str text: initial text
+    :param integer length: maximum length of resulting text
+    :param integer max: if > 0, *text* is shortened only if remaining text is longer than max
 
     >>> from pyams_utils.text import get_text_start
     >>> get_text_start('This is a long string', 10)
@@ -61,7 +61,10 @@
 
 @adapter_config(name='raw', context=(str, IRequest), provides=IHTMLRenderer)
 class BaseHTMLRenderer(object):
-    """Raw text renderer utility class"""
+    """Raw text HTML renderer
+
+    This renderer renders input text 'as is', mainly for use in a <pre> tag.
+    """
 
     def __init__(self, context, request):
         self.context = context
@@ -73,7 +76,10 @@
 
 @adapter_config(name='text', context=(str, IRequest), provides=IHTMLRenderer)
 class TextRenderer(BaseHTMLRenderer):
-    """Render raw text to HTML"""
+    """Basic text HTML renderer
+
+    This renderer only replace newlines with HTML breaks.
+    """
 
     def render(self, **kwargs):
         return html.escape(self.context).replace('\n', '<br />\n')
@@ -81,7 +87,10 @@
 
 @adapter_config(name='rest', context=(str, IRequest), provides=IHTMLRenderer)
 class ReStructuredTextRenderer(BaseHTMLRenderer):
-    """Render reStructuredText to HTML"""
+    """reStructuredText HTML renderer
+
+    This renderer is using *docutils* to render HTML output.
+    """
 
     def render(self, **kwargs):
         """Render reStructuredText to HTML"""
@@ -100,7 +109,10 @@
 
 
 def text_to_html(text, renderer='text'):
-    """Convert text to HTML using the given renderer"""
+    """Convert text to HTML using the given renderer
+
+    Renderer name can be any registered HTML renderer adapter
+    """
     request = check_request()
     registry = request.registry
     renderer = registry.queryMultiAdapter((text, request), IHTMLRenderer, name=renderer)
@@ -110,16 +122,22 @@
 
 @adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension)
 class HTMLTalesExtension(ContextRequestViewAdapter):
-    """extension:html TALES expression"""
+    """*extension:html* TALES expression
 
-    def render(self, context=None):
+    If first *context* argument of the renderer is an object for which an :py:class:`IHTMLRenderer`
+    can be found, this adapter is used to render the context to HTML; if *context* is a string,
+    it is converted to HTML using the renderer defined as second parameter; otherwise, context is just
+    converted to string using the :py:func:`str` function.
+    """
+
+    def render(self, context=None, renderer='text'):
         if context is None:
             context = self.context
-        renderer = self.request.registry.queryMultiAdapter((context, self.request, self.view), IHTMLRenderer)
-        if renderer is not None:
-            return renderer.render()
+        adapter = self.request.registry.queryMultiAdapter((context, self.request, self.view), IHTMLRenderer)
+        if adapter is not None:
+            return adapter.render()
         elif isinstance(context, str):
-            return text_to_html(context, 'text')
+            return text_to_html(context, renderer)
         else:
             return str(context)
 
--- a/src/pyams_utils/timezone/utility.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/timezone/utility.py	Fri Nov 18 15:28:54 2016 +0100
@@ -22,18 +22,18 @@
 
 # import packages
 from persistent import Persistent
+from pyams_utils.property import DocFieldProperty
 from pyams_utils.registry import utility_config
 from pyams_utils.site import check_required_utilities
 from pyramid.events import subscriber
 from zope.container.contained import Contained
 from zope.interface import implementer
-from zope.schema.fieldproperty import FieldProperty
 
 
 @implementer(IServerTimezone)
 class ServerTimezoneUtility(Persistent, Contained):
 
-    timezone = FieldProperty(IServerTimezone['timezone'])
+    timezone = DocFieldProperty(IServerTimezone['timezone'])
 
 
 REQUIRED_UTILITIES = ((IServerTimezone, '', ServerTimezoneUtility, 'Server timezone'),)
--- a/src/pyams_utils/traversing.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/traversing.py	Fri Nov 18 15:28:54 2016 +0100
@@ -38,8 +38,8 @@
 
     This is an upgraded version of native Pyramid traverser.
     It adds:
-     - a new BeforeTraverseEvent before traversing each object in the path
-     - support for namespaces with "++" notation
+    - a new BeforeTraverseEvent before traversing each object in the path
+    - support for namespaces with "++" notation
     """
 
     NAMESPACE_SELECTOR = '++'
@@ -170,11 +170,11 @@
 def get_parent(context, interface=Interface, allow_context=True, condition=None):
     """Get first parent of the context that implements given interface
 
-    @context: base element
-    @interface: the interface that parend should implement
-    @allow_context: if 'True' (the default), traversing is done starting with context; otherwise,
+    :param object context: base element
+    :param Interface interface: the interface that parend should implement
+    :param boolean allow_context: if 'True' (the default), traversing is done starting with context; otherwise,
         traversing is done starting from context's parent
-    @condition: an optional function that should return a 'True' result when called with parent
+    :param callable condition: an optional function that should return a 'True' result when called with parent
         as first argument
     """
     if allow_context:
--- a/src/pyams_utils/unicode.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/unicode.py	Fri Nov 18 15:28:54 2016 +0100
@@ -79,15 +79,28 @@
 
 def translate_string(s, escape_slashes=False, force_lower=True,
                      spaces=' ', remove_punctuation=True, keep_chars='_-.'):
-    """Remove extended characters from string and replace them with 'basic' ones
+    """Remove extended characters and diacritics from string and replace them with 'basic' ones
     
-    @param s: text to be cleaned.
-    @param escape_slashes: if True, slashes are also converted
-    @param force_lower: if True, result is automatically converted to lower case
-    @param spaces: character used to replace spaces
-    @param remove_punctuation: if True, all punctuation characters are removed
-    @param keep_chars: punctuation characters which may be kept
-    @return: text without diacritics
+    :param str s: text to be cleaned.
+    :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
+    :param boolean remove_punctuation: if True, all punctuation characters are removed
+    :param str keep_chars: characters which may be kept in the input string
+    :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)
+    'ceci est un test en francais'
+    >>> translate_string(input, force_lower=False)
+    'Ceci est un test en Francais'
+    >>> translate_string(input, spaces='-')
+    'ceci-est-un-test-en-francais'
+    >>> translate_string(input, remove_punctuation=False)
+    'ceci est un test en francais !!!'
+    >>> translate_string(input, keep_chars='!')
+    'ceci est un test en francais !!!'
     """
     if escape_slashes:
         s = s.replace("\\", "/").split("/")[-1]
@@ -109,9 +122,17 @@
 def nvl(value, default=''):
     """Get specified value, or an empty string if value is empty
     
-    @param value: text to be checked
-    @param default: default value
-    @return: value, or default 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*
+
+    >>> from pyams_utils.unicode import nvl
+    >>> nvl(None)
+    ''
+    >>> nvl('foo')
+    'foo'
+    >>> nvl(False, 'bar')
+    'bar'
     """
     return value or default
 
@@ -119,11 +140,20 @@
 def uninvl(value, default='', encoding='utf-8'):
     """Get specified value converted to unicode, or an empty unicode string if value is empty
     
-    @param value: text to be checked
-    @type value: str or unicode
-    @param default: default value
-    @return: value, or default if value is empty
-    @rtype: unicode
+    :param str/bytes value: the input to be checked
+    :param default: str; default value
+    :param encoding: str; encoding name to use for conversion
+    :return: str; value, or *default* if value is empty, converted to unicode
+
+    >>> from pyams_utils.unicode import uninvl
+    >>> uninvl('String value')
+    'String value'
+    >>> uninvl(b'String value')
+    'String value'
+    >>> uninvl(b'Cha\\xc3\\xaene accentu\\xc3\\xa9e')
+    'Chaîne accentuée'
+    >>> uninvl(b'Cha\\xeene accentu\\xe9e', 'latin1')
+    'Chaîne accentuée'
     """
     if isinstance(value, str):
         return value
@@ -136,10 +166,14 @@
 def unidict(value, encoding='utf-8'):
     """Get specified dict with values converted to unicode
     
-    @param value: input dict of strings which may be converted to unicode
-    @type value: dict
-    @return: input dict converted to unicode
-    @rtype: dict
+    :param dict value: input mapping of strings which may be converted to unicode
+    :return: dict; a new mapping with each value converted to unicode
+
+    >>> from pyams_utils.unicode import unidict
+    >>> unidict({'input': b'Cha\\xc3\\xaene accentu\\xc3\\xa9e'})
+    {'input': 'Chaîne accentuée'}
+    >>> unidict({'input': b'Cha\\xeene accentu\\xe9e'}, 'latin1')
+    {'input': 'Chaîne accentuée'}
     """
     result = {}
     for key in value:
@@ -150,10 +184,14 @@
 def unilist(value, encoding='utf-8'):
     """Get specified list with values converted to unicode
     
-    @param value: input list of strings which may be converted to unicode
-    @type value: list
-    @return: input list converted to unicode
-    @rtype: list
+    :param list value: input list of strings which may be converted to unicode
+    :return: list; a new list with each value converted to unicode
+
+    >>> from pyams_utils.unicode import unilist
+    >>> unilist([b'Cha\\xc3\\xaene accentu\\xc3\\xa9e'])
+    ['Chaîne accentuée']
+    >>> unilist([b'Cha\\xeene accentu\\xe9e'], 'latin1')
+    ['Chaîne accentuée']
     """
     if not isinstance(value, (list, tuple)):
         return uninvl(value, encoding)
@@ -161,15 +199,45 @@
 
 
 def encode(value, encoding='utf-8'):
-    """Encode given Unicode value to bytes with given encoding"""
+    """Encode given Unicode value to bytes with given encoding
+
+    :param str value: the value to encode
+    :param str encoding: selected encoding
+    :return: bytes; value encoded to bytes if input is a string, original value otherwise
+
+    >>> from pyams_utils.unicode import encode
+    >>> encode('Chaîne accentuée')
+    b'Cha\\xc3\\xaene accentu\\xc3\\xa9e'
+    >>> encode('Chaîne accentuée', 'latin1')
+    b'Cha\\xeene accentu\\xe9e'
+    """
     return value.encode(encoding) if isinstance(value, str) else value
 
 
 def utf8(value):
-    """Encode given unicode value to UTF-8 encoded bytes"""
+    """Encode given unicode value to UTF-8 encoded bytes
+
+    :param str value: the value to encode to utf-8
+    :return: bytes; value encoded to bytes if input is a string, original value otherwise
+
+    >>> from pyams_utils.unicode import utf8
+    >>> utf8('Chaîne accentuée')
+    b'Cha\\xc3\\xaene accentu\\xc3\\xa9e'
+    """
     return encode(value, 'utf-8')
 
 
 def decode(value, encoding='utf-8'):
-    """Decode given bytes value to unicode with given encoding"""
+    """Decode given bytes value to unicode with given encoding
+
+    :param bytes value: the value to decode
+    :param str encoding: selected encoding
+    :return: str; value decoded to unicode string if input is a bytes, original value otherwise
+
+    >>> from pyams_utils.unicode import decode
+    >>> decode(b'Cha\\xc3\\xaene accentu\\xc3\\xa9e')
+    'Chaîne accentuée'
+    >>> decode(b'Cha\\xeene accentu\\xe9e', 'latin1')
+    'Chaîne accentuée'
+    """
     return value.decode(encoding) if isinstance(value, bytes) else value
--- a/src/pyams_utils/url.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/url.py	Fri Nov 18 15:28:54 2016 +0100
@@ -26,7 +26,16 @@
 
 
 def absolute_url(context, request, view_name=None):
-    """Get resource absolute_url"""
+    """Get resource absolute_url
+
+    :param object context: the persistent object for which absolute URL is required
+    :param request: the request on which URL is based
+    :param str view_name: an optional view name to add to URL
+
+    This absolute URL function is based on default Pyramid's :py:func:`resource_url` function, but
+    add checks to remove some double slashes, and add control on view name when it begins with a '#'
+    character which is used by MyAMS.js framework.
+    """
     # if we have several parents without name in the lineage, the resource URL contains a double slash
     # which generates "NotFound" exceptions; so we replace it with a single slash...
     result = resource_url(context, request).replace('//', '/').replace(':/', '://')
@@ -42,7 +51,10 @@
 
 @adapter_config(name='absolute_url', context=(IPersistent, Interface, Interface), provides=ITALESExtension)
 class AbsoluteUrlTalesExtension(ContextRequestViewAdapter):
-    """extension:absolute_url(context) TALES extension"""
+    """extension:absolute_url(context, view_name) TALES extension
+
+    A PyAMS TALES extension used to get access to an object URL from a page template.
+    """
 
     def render(self, context=None, view_name=None):
         if context is None:
--- a/src/pyams_utils/vocabulary.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/vocabulary.py	Fri Nov 18 15:28:54 2016 +0100
@@ -26,7 +26,28 @@
 
 
 class vocabulary_config:
-    """Class decorator to define a vocabulary"""
+    """Class decorator to define a vocabulary
+
+    :param str name: name of the registered vocabulary
+
+    This is, for example, how a vocabulary of registered ZEO connections utilities is created:
+
+    .. code-block:: python
+
+        from pyams_utils.interfaces.zeo import IZEOConnection
+
+        from pyams_utils.registry import get_utilities_for
+        from pyams_utils.vocabulary import voacbulary_config
+        from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+
+        @vocabulary_config(name='PyAMS ZEO connections')
+        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)]
+                super(ZEOConnectionVocabulary, self).__init__(terms)
+    """
 
     def __init__(self, name):
         self.name = name
--- a/src/pyams_utils/wsgi.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/wsgi.py	Fri Nov 18 15:28:54 2016 +0100
@@ -22,9 +22,8 @@
 def wsgi_environ_cache(*names):
     """Wrap a function/method to cache its result for call into request.environ
 
-    :param list[string] names: keys to cache into environ, the len(names) must
-    be equal to the result's length or scalar
-    :return:
+    :param [string...] names: keys to cache into environ; len(names) must
+        be equal to the result's length or scalar
     """
     def decorator(fn):
         def function_wrapper(self, request):
--- a/src/pyams_utils/zodb.py	Tue Nov 15 10:43:55 2016 +0100
+++ b/src/pyams_utils/zodb.py	Fri Nov 18 15:28:54 2016 +0100
@@ -27,6 +27,7 @@
 # import packages
 from persistent import Persistent
 from pyams_utils.adapter import adapter_config
+from pyams_utils.property import DocFieldProperty
 from pyams_utils.registry import get_utilities_for
 from pyams_utils.vocabulary import vocabulary_config
 from pyramid.events import subscriber
@@ -36,13 +37,12 @@
 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
 
 
 @adapter_config(context=IPersistent, provides=IConnection)
 def get_connection(obj):
-    """An adapter which gets a ZODB connection of a persistent object.
+    """An adapter which gets a ZODB connection from a persistent object
 
     We are assuming the object has a parent if it has been created in
     this transaction.
@@ -73,35 +73,72 @@
 
 @implementer(IZEOConnection)
 class ZEOConnection(object):
-    """ZEO connection object"""
+    """ZEO connection object
+
+    This object can be used to store all settings to be able to open a ZEO connection.
+
+    Note that a ZEO connection object is a context manager, so you can use it like this:
+
+    .. code-block:: python
+
+        from pyams_utils.zodb import ZEOConnection
+
+        def my_method(zeo_settings):
+            zeo_connection = ZEOConnection()
+            zeo_connection.update(zeo_settings)
+            with zeo_connection as root:
+                # *root* is then the ZODB root object
+                # do whatever you want with ZEO connection,
+                # which is closed automatically
+    """
 
     _storage = None
     _db = None
     _connection = None
 
-    name = FieldProperty(IZEOConnection['name'])
-    server_name = FieldProperty(IZEOConnection['server_name'])
-    server_port = FieldProperty(IZEOConnection['server_port'])
-    storage = FieldProperty(IZEOConnection['storage'])
-    username = FieldProperty(IZEOConnection['username'])
-    password = FieldProperty(IZEOConnection['password'])
-    server_realm = FieldProperty(IZEOConnection['server_realm'])
-    blob_dir = FieldProperty(IZEOConnection['blob_dir'])
-    shared_blob_dir = FieldProperty(IZEOConnection['shared_blob_dir'])
+    name = DocFieldProperty(IZEOConnection['name'])
+    server_name = DocFieldProperty(IZEOConnection['server_name'])
+    server_port = DocFieldProperty(IZEOConnection['server_port'])
+    storage = DocFieldProperty(IZEOConnection['storage'])
+    username = DocFieldProperty(IZEOConnection['username'])
+    password = DocFieldProperty(IZEOConnection['password'])
+    server_realm = DocFieldProperty(IZEOConnection['server_realm'])
+    blob_dir = DocFieldProperty(IZEOConnection['blob_dir'])
+    shared_blob_dir = DocFieldProperty(IZEOConnection['shared_blob_dir'])
 
     def get_settings(self):
+        """Get mapping of all connection settings
+
+        These settings can be converted to JSON and sent to another process, for example
+        via a ØMQ connection.
+
+        :return: dict
+        """
         result = {}
         for name in getFieldNames(IZEOConnection):
             result[name] = getattr(self, name)
         return result
 
     def update(self, settings):
+        """Update connection properties with settings as *dict*
+
+        :param dict settings: typically extracted via the :py:meth:`get_settings` method from
+            another process
+        """
         names = getFieldNames(IZEOConnection)
         for key, value in settings.items():
             if key in names:
                 setattr(self, key, value)
 
     def get_connection(self, wait=False, get_storage=False):
+        """Create ZEO client connection from current settings
+
+        :param boolean wait: should connection wait until storage is ready
+        :param boolean get_storage: if *True*, the method should return a tuple containing
+            storage and DB objects; otherwise only DB object is returned
+        :return: tuple containing ZEO client storage and DB object (if *get_storage* argument is
+            set to *True*), or only DB object otherwise
+        """
         storage = ClientStorage.ClientStorage((self.server_name, self.server_port),
                                               storage=self.storage,
                                               username=self.username or '',