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