--- 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 < ò > 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 '',