# HG changeset patch # User Thierry Florac # Date 1479479334 -3600 # Node ID 9049384a2bd48b02dcf36c9ab49630da49a188a5 # Parent 01d01045a2b724ced457be89c7840ce34aff5bc0 Updated documentation diff -r 01d01045a2b7 -r 9049384a2bd4 docs/HISTORY.txt --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 docs/Makefile --- /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 ' where 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." diff -r 01d01045a2b7 -r 9049384a2bd4 docs/make.bat --- /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 ^` where ^ 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 diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/conf.py --- /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 ' +author = 'Thierry Florac ' + +# 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. +# " v 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 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 diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/index.rst --- /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` diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/install.rst --- /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 `_ 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. diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/modules.rst --- /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 diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.interfaces.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.protocol.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.scripts.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.tests.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.timezone.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.widget.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/pyams_utils.zmi.rst --- /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: diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/site.rst --- /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 ` 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 ` 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. diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/traverser.rst --- /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 ` 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 ` 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... diff -r 01d01045a2b7 -r 9049384a2bd4 docs/source/zca.rst --- /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) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/adapter.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/attr.py --- 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: diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/container.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/context.py --- 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): diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/data.py --- 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 + +
...
+ +After data initialization by **MyAMS.js**, the following code will be converted to: + +.. code-block:: html + +
...
+""" + __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:: + +
...
+ """ 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:: + +
...
+ """ 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:: + +
...
+ """ def render(self, params=None): + """See `pyams_utils.interfaces.tales.ITALESExtension` interface""" return self.request.annotations.get(params) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/date.py --- 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=) """ 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) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/encoding.py --- 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: diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/html.py --- 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 = '''

Header

This is an < ò > entity.

''' >>> html_to_text(html) 'Header\\nThis is an < o > entity.\\n\\n' - """ if value is None: return '' diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/interfaces/__init__.py --- 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): diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/intids.py --- 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) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/list.py --- 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] diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo Binary file src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo has changed diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po --- 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 \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" diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/locales/pyams_utils.pot --- 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 , 2015. +# FIRST AUTHOR , 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 \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 "" diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/lock.py --- 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): diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/progress.py --- 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']) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/property.py --- 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): diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/protocol/http.py --- 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], diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/registry.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/request.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/session.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/site.py --- 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 ` 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: diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/size.py --- 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) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/tales.py --- 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 ` + 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 + + + + 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 + + + + + + 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') diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/text.py --- 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
 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', '
\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) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/timezone/utility.py --- 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'),) diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/traversing.py --- 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: diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/unicode.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/url.py --- 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: diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/vocabulary.py --- 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 diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/wsgi.py --- 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): diff -r 01d01045a2b7 -r 9049384a2bd4 src/pyams_utils/zodb.py --- 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 '',