--- a/.gitlab-ci.yml Fri Nov 22 18:57:44 2019 +0100
+++ b/.gitlab-ci.yml Tue Nov 26 10:15:05 2019 +0100
@@ -5,6 +5,12 @@
- dist
- quality
+cache:
+ paths:
+ - bin/
+ - eggs/
+ - parts/
+
before_script:
- export http_proxy=http://172.17.0.1:3128/
- export HTTP_PROXY=http://172.17.0.1:3128/
@@ -29,6 +35,7 @@
artifacts:
paths:
- ./dist
+
pylint:
stage: quality
allow_failure: true
@@ -47,17 +54,32 @@
stage: quality
allow_failure: true
image: docker:stable
+ services:
+ - docker:stable-dind
variables:
DOCKER_DRIVER: overlay2
- services:
- - docker:stable-dind
+ DOCKER_TLS_CERTDIR: ""
script:
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
+ - |
+ if ! docker info &>/dev/null; then
+ if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
+ export DOCKER_HOST='tcp://localhost:2375'
+ fi
+ fi
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
+ "registry.gitlab.com/gitlab-org/security-products/codequality:12-0-stable" /code
artifacts:
reports:
codequality: gl-code-quality-report.json
+ expire_in: 1 week
+ dependencies: []
+ only:
+ refs:
+ - branches
+ - tags
+ except:
+ variables:
+ - $CODE_QUALITY_DISABLED
--- a/.hgignore Fri Nov 22 18:57:44 2019 +0100
+++ b/.hgignore Tue Nov 26 10:15:05 2019 +0100
@@ -6,6 +6,8 @@
syntax: regexp
^bin$
syntax: regexp
+^eggs$
+syntax: regexp
^\.installed\.cfg$
syntax: regexp
^\.settings$
--- a/buildout.cfg Fri Nov 22 18:57:44 2019 +0100
+++ b/buildout.cfg Tue Nov 26 10:15:05 2019 +0100
@@ -1,11 +1,11 @@
[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
versions = versions
-#allow-picked-versions = false
+allow-picked-versions = false
show-picked-versions = true
newest = false
@@ -55,7 +55,9 @@
[pylint]
recipe = zc.recipe.egg
-eggs = pylint
+eggs =
+ pyams_utils
+ pylint
entry-points = pylint=pylint.lint:Run
arguments = sys.argv[1:]
--- a/docs/README.txt Fri Nov 22 18:57:44 2019 +0100
+++ b/docs/README.txt Tue Nov 26 10:15:05 2019 +0100
@@ -1,14 +1,25 @@
===================
-pyams_utils package
+PyAMS_utils package
===================
.. contents::
-What is pyams_utils ?
+What is PyAMS
+=============
+
+PyAMS (Pyramid Application Management Suite) is a small suite of packages written for applications
+and content management with the Pyramid framework.
+
+**PyAMS** is actually mainly used to manage web sites through content management applications (CMS,
+see PyAMS_content package), but many features are generic and can be used inside any kind of web
+application.
+
+
+What is PyAMS_utils ?
=====================
-pyams_utils is a set of classes and functions which can be used to provide many small services and
+PyAMS_utils is a set of classes and functions which can be used to provide many small services and
handle common operations in the context of a Pyramid application.
Internal sub-packages include:
@@ -30,7 +41,4 @@
How to use pyams_utils ?
========================
-A set of pyams_utils usages are given as doctests in pyams_utils/doctests/README.txt
-
-You will also a whole set of documentations into the "docs" directory, available on
-`ReadTheDocs <http://pyams-utils.readthedocs.org>`_
+A whole set of PyAMS documentation is available on `ReadTheDocs <https://pyams.readthedocs.io>`_
--- a/src/pyams_utils.egg-info/PKG-INFO Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils.egg-info/PKG-INFO Tue Nov 26 10:15:05 2019 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pyams-utils
-Version: 0.1.34.2
+Version: 0.1.35
Summary: Utility functions and classes for PyAMS
Home-page: http://www.ztfy.org
Author: Thierry Florac
@@ -47,6 +47,10 @@
Changelog
=========
+ 0.1.35
+ ------
+ - Pylint code cleanup and GitLab-CI integration updates
+
0.1.34.2
--------
- correction in "generate_url" function
--- a/src/pyams_utils.egg-info/SOURCES.txt Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils.egg-info/SOURCES.txt Tue Nov 26 10:15:05 2019 +0100
@@ -51,13 +51,13 @@
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
src/pyams_utils/interfaces/data.py
src/pyams_utils/interfaces/inherit.py
src/pyams_utils/interfaces/intids.py
+src/pyams_utils/interfaces/pygments.py
src/pyams_utils/interfaces/site.py
src/pyams_utils/interfaces/size.py
src/pyams_utils/interfaces/tales.py
--- a/src/pyams_utils/adapter.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/adapter.py Tue Nov 26 10:15:05 2019 +0100
@@ -27,7 +27,7 @@
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, get_global_registry
+from pyams_utils.registry import get_current_registry
__docformat__ = 'restructuredtext'
--- a/src/pyams_utils/context.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/context.py Tue Nov 26 10:15:05 2019 +0100
@@ -50,7 +50,7 @@
sys.stderr = err
-class ContextSelector(object): # pylint: disable=too-few-public-methods
+class ContextSelector: # 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
--- a/src/pyams_utils/decorator.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/decorator.py Tue Nov 26 10:15:05 2019 +0100
@@ -63,6 +63,5 @@
if len(msg) == 1 and callable(msg[0]):
message = u''
return decorator(msg[0])
- else:
- message = msg[0]
- return decorator
+ message = msg[0]
+ return decorator
--- a/src/pyams_utils/include.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/include.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,6 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_utils.include module
+
+This module is used for Pyramid integration
+"""
+
from chameleon import PageTemplateFile
from persistent.interfaces import IPersistent
from z3c.pt.pagetemplate import PageTemplateFile as Z3cPageTemplateFile
@@ -60,7 +65,7 @@
config.registry.registerAdapter(KeyReferenceToPersistent, (IPersistent, ), IKeyReference)
try:
- import pyams_zmi
+ import pyams_zmi # pylint: disable=import-outside-toplevel,unused-import
except ImportError:
config.scan(ignore='pyams_utils.zmi')
else:
--- a/src/pyams_utils/interfaces/__init__.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/__init__.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,18 +10,20 @@
# FOR A PARTICULAR PURPOSE.
#
-"""PyAMS_utils interfaces module
+"""PyAMS_utils.interfaces package
This module defines several generic constants and interfaces.
+
It is also used to provide translations to common zope.schema exceptions.
"""
-__docformat__ = 'restructuredtext'
+from zope.interface import Interface
+from zope.schema.interfaces import ConstraintNotSatisfied, InvalidDottedName, InvalidId, \
+ InvalidURI, InvalidValue, NotAContainer, NotAnIterator, NotUnique, RequiredMissing, \
+ SchemaNotFullyImplemented, SchemaNotProvided, TooBig, TooLong, TooShort, TooSmall, Unbound, \
+ WrongContainedType, WrongType
-from zope.interface import Interface
-from zope.schema.interfaces import ConstraintNotSatisfied, InvalidDottedName, InvalidId, InvalidURI, InvalidValue, \
- NotAContainer, NotAnIterator, NotUnique, RequiredMissing, SchemaNotFullyImplemented, SchemaNotProvided, TooBig, \
- TooLong, TooShort, TooSmall, Unbound, WrongContainedType, WrongType
+__docformat__ = 'restructuredtext'
from pyams_utils import _
@@ -50,7 +52,6 @@
NotAContainer.__doc__ = _("""Not a container""")
NotAnIterator.__doc__ = _("""Not an iterator""")
-
#
# Custom permissions
#
@@ -65,7 +66,8 @@
'''View permission is a custom permission used to view contents'''
MANAGE_PERMISSION = 'manage'
-'''Permission used to manage basic information; this permission is generally not used by custom contents'''
+'''Permission used to manage basic information; this permission is generally not used by custom
+contents'''
VIEW_SYSTEM_PERMISSION = 'pyams.ViewSystem'
'''Permission used to access management screens'''
@@ -106,14 +108,15 @@
This interface can be used to register an "interface's object factory".
For a given interface, such factory can be used to get an instance of an object providing
- this interface; several factories can be registered for the same interface if they have distinct
- names. See :py:mod:`pyams_utils.factory` module.
+ this interface; several factories can be registered for the same interface if they have
+ distinct names. See :py:mod:`pyams_utils.factory` module.
"""
class ICacheKeyValue(Interface):
"""Interface used to get string representation of a given object as cache key
- Several default adapters are given for objects (using their "id()"), strings (using string as key)
- and for persistent objects (using their persistent OID); you are free to provide your own adapters.
+ Several default adapters are given for objects (using their "id()"), strings (using string as
+ key) and for persistent objects (using their persistent OID); you are free to provide your
+ own adapters.
"""
--- a/src/pyams_utils/interfaces/data.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/data.py Tue Nov 26 10:15:05 2019 +0100
@@ -17,11 +17,11 @@
HTML code.
"""
-__docformat__ = 'restructuredtext'
-
from zope.interface import Interface
from zope.schema import Dict
+__docformat__ = 'restructuredtext'
+
class IObjectData(Interface):
"""Object data generic interface
--- a/src/pyams_utils/interfaces/inherit.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/inherit.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,17 +10,19 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.inherit module
-# import standard library
+This module defines interfaces which are used by the :py:mod:`inherit <pyams_utils.inherit>`
+module
+"""
-# import interfaces
from pyramid.interfaces import ILocation
-# import packages
from zope.interface import Attribute
from zope.schema import Bool
+__docformat__ = 'restructuredtext'
+
from pyams_utils import _
--- a/src/pyams_utils/interfaces/intids.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/intids.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,15 +10,16 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.intids module
-# import standard packages
+Small set of interfaces used by IIntIds utilities.
+"""
-# import interfaces
from zope.interface import Interface
+from zope.schema import Int, TextLine
-# import packages
-from zope.schema import TextLine, Int
+
+__docformat__ = 'restructuredtext'
#
@@ -35,5 +36,6 @@
"""Interface used to get unique ID of an object"""
oid = TextLine(title="Unique ID",
- description="Globally unique identifier of this object can be used to create internal links",
+ description="Globally unique identifier of this object can be used to create "
+ "internal links",
readonly=True)
--- a/src/pyams_utils/interfaces/site.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/site.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,6 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_utils.interfaces.site module
+
+This module provides interfaces related to site management, generations and utilities
+"""
+
from zope.annotation.interfaces import IAttributeAnnotatable
from zope.interface import Attribute, Interface
from zope.interface.interfaces import IObjectEvent
--- a/src/pyams_utils/interfaces/size.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/size.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,17 +10,16 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_utils.interfaces.size module
-# import standard library
+This module provides a simple interface for objects handling length attribute
+"""
-# import interfaces
-
-# import packages
from zope.interface import Interface
from zope.schema import Int
+__docformat__ = 'restructuredtext'
+
class ILength(Interface):
"""Length interface"""
--- a/src/pyams_utils/interfaces/tales.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/tales.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,14 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.tales module
-# import standard library
+TALES extensions are custom adapters which can be used to extend Chameleon and Zope templates.
+"""
-# import interfaces
+from zope.interface import Interface
-# import packages
-from zope.interface import Interface
+
+__docformat__ = 'restructuredtext'
class ITALESExtension(Interface):
--- a/src/pyams_utils/interfaces/text.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/text.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,15 +10,16 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.text module
+
+This module provides a single interface used by HTML rendering adapters, which are used to convert
+any object to an HTML representation.
+"""
+
+from zope.interface import Attribute, Interface
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Attribute, Interface
+__docformat__ = 'restructuredtext'
from pyams_utils import _
--- a/src/pyams_utils/interfaces/timezone.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/timezone.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,17 +10,17 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.timezone module
+
+This module provides timezone utility interface and schema field
+"""
+
+from zope.interface import Interface, implementer
+from zope.schema import Choice
+from zope.schema.interfaces import IChoice
-# import standard library
-
-# import interfaces
-from zope.schema.interfaces import IChoice
-
-# import packages
-from zope.interface import implementer, Interface
-from zope.schema import Choice
+__docformat__ = 'restructuredtext'
from pyams_utils import _
--- a/src/pyams_utils/interfaces/traversing.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/traversing.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,16 +10,18 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.traversing module
+
+The IPathElements is used by an Hypatia index to store internal IDs of parents of a given
+objects; this allows to query catalog for objects which are located "inside" a given parent
+object, identified by it's internal ID.
+"""
+
+from zope.interface import Interface
+from zope.schema import Int, List
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Interface
-from zope.schema import List, Int
+__docformat__ = 'restructuredtext'
class IPathElements(Interface):
--- a/src/pyams_utils/interfaces/tree.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/tree.py Tue Nov 26 10:15:05 2019 +0100
@@ -1,7 +1,5 @@
-### -*- coding: utf-8 -*- ####################################################
-##############################################################################
#
-# Copyright (c) 2012 Thierry Florac <tflorac AT ulthar.net>
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
@@ -11,7 +9,11 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
-##############################################################################
+
+"""PyAMS_utils.interfaces.tree module
+
+The interfaces provided by this module are used to manage trees.
+"""
from zope.interface import Interface, Attribute
--- a/src/pyams_utils/interfaces/url.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/url.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,11 +10,20 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.url module
+
+These interfaces are used to define different types of URLs which can be used in a web site.
+These includes absolute URLs, canonical URLs and related URLs.
+
+See :py:mod:`PyAMS URL module <pyams_utils.url>` for a longer description.
+"""
from zope.interface import Interface
+__docformat__ = 'restructuredtext'
+
+
class ICanonicalURL(Interface):
"""Interface used to get content's canonical URL"""
--- a/src/pyams_utils/interfaces/zeo.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/interfaces/zeo.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,16 +10,16 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.interfaces.zeo module
+
+This module provides interface definition for a ZEO connection
+"""
+
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Int, Password, TextLine
-# import standard library
-
-# import interfaces
-
-# import packages
-from zope.interface import Interface, Attribute
-from zope.schema import TextLine, Int, Password, Bool
+__docformat__ = 'restructuredtext'
from pyams_utils import _
@@ -51,11 +51,13 @@
required=False)
password = Password(title=_("ZEO password"),
- description=_("User password on ZEO server; only for ZEO server before 5.0"),
+ description=_(
+ "User password on ZEO server; only for ZEO server before 5.0"),
required=False)
server_realm = TextLine(title=_("ZEO server realm"),
- description=_("Realm name on ZEO server; only for ZEO server before 5.0"),
+ description=_(
+ "Realm name on ZEO server; only for ZEO server before 5.0"),
required=False)
blob_dir = TextLine(title=_("BLOBs directory"),
@@ -63,8 +65,9 @@
required=False)
shared_blob_dir = Bool(title=_("Shared BLOBs directory ?"),
- description=_("""Flag whether the blob_dir is a server-shared filesystem """
- """that should be used instead of transferring blob data over zrpc."""),
+ description=_(
+ "Flag whether the blob_dir is a server-shared filesystem "
+ "that should be used instead of transferring blob data over zrpc."),
required=True,
default=False)
--- a/src/pyams_utils/protocol/http.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/protocol/http.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,49 +10,54 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_utils.protocol.http module
+
+This module provides an HTTP client class, which allows to easilly define proxies and
+authentication headers.
+"""
+
+import urllib.parse
+
+import httplib2
+
+
__docformat__ = 'restructuredtext'
-# import standard library
-import httplib2
-import urllib.parse
-
-# import interfaces
-
-# import packages
-
-
-class HTTPClient(object):
+class HTTPClient:
+ # pylint: disable=too-many-instance-attributes
"""HTTP client with proxy support"""
- def __init__(self, method, protocol, servername, url, params={}, credentials=(),
- proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+ def __init__(self, method, protocol, servername, url, params=None, credentials=(),
+ proxy=(), rdns=True, proxy_auth=(), timeout=None, headers=None):
+ # pylint: disable=too-many-arguments
"""Intialize HTTP connection"""
self.connection = None
self.method = method
self.protocol = protocol
self.servername = servername
self.url = url
- self.params = params
+ self.params = params or {}
self.location = None
self.credentials = credentials
self.proxy = proxy
self.rdns = rdns
self.proxy_auth = proxy_auth
self.timeout = timeout
- self.headers = headers
- if 'User-Agent' not in headers:
+ self.headers = headers or {}
+ if 'User-Agent' not in self.headers:
self.headers['User-Agent'] = 'PyAMS HTTP Client/1.0'
def get_response(self):
"""Common HTTP request"""
if self.proxy and (len(self.proxy) == 2):
- proxy_info = httplib2.ProxyInfo(httplib2.socks.PROXY_TYPE_HTTP,
- proxy_host=self.proxy[0],
- proxy_port=self.proxy[1],
- proxy_rdns=self.rdns,
- proxy_user=self.proxy_auth and self.proxy_auth[0] or None,
- proxy_pass=self.proxy_auth and self.proxy_auth[1] or None)
+ proxy_info = httplib2.ProxyInfo(
+ httplib2.socks.PROXY_TYPE_HTTP,
+ proxy_host=self.proxy[0],
+ proxy_port=self.proxy[1],
+ proxy_rdns=self.rdns,
+ proxy_user=self.proxy_auth and self.proxy_auth[0] or None,
+ proxy_pass=self.proxy_auth and self.proxy_auth[1] or None)
else:
proxy_info = None
http = httplib2.Http(timeout=self.timeout, proxy_info=proxy_info)
@@ -65,14 +70,17 @@
return response, content
-def get_client(method, protocol, servername, url, params={}, credentials=(), proxy=(),
- rdns=True, proxy_auth=(), timeout=None, headers={}):
+def get_client(method, protocol, servername, url, params=None, credentials=(), proxy=(),
+ rdns=True, proxy_auth=(), timeout=None, headers=None):
+ # pylint: disable=too-many-arguments
"""HTTP client factory"""
return HTTPClient(method, protocol, servername, url, params, credentials, proxy,
rdns, proxy_auth, timeout, headers)
-def get_client_from_url(url, credentials=(), proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+def get_client_from_url(url, credentials=(), proxy=(), rdns=True, proxy_auth=(), timeout=None,
+ headers=None):
+ # pylint: disable=too-many-arguments
"""HTTP client factory from URL"""
elements = urllib.parse.urlparse(url)
return HTTPClient('GET', elements.scheme, elements.netloc, elements.path, elements.params,
--- a/src/pyams_utils/protocol/tcp.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/protocol/tcp.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,12 +10,17 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.protocol.tcp module
+
+This module only provides a single function, used to know if a given TCP port is already in use
+"""
import socket
+__docformat__ = 'restructuredtext'
+
def is_port_in_use(port, hostname='localhost'):
"""Check if given port is already used locally"""
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- return s.connect_ex((hostname, port)) == 0
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+ return sock.connect_ex((hostname, port)) == 0
--- a/src/pyams_utils/protocol/xmlrpc.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/protocol/xmlrpc.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,6 +10,13 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_utils.protocol.xmlrpc module
+
+This module provides a few set of classes and functions usable to improve XML-RPC client usage.
+
+It provides custom transports and allows storage of response cookies
+"""
+
import base64
import http.client
import http.cookiejar
@@ -17,7 +24,6 @@
import urllib.request
import xmlrpc.client
-
try:
import gzip
except ImportError:
@@ -27,6 +33,7 @@
class XMLRPCCookieAuthTransport(xmlrpc.client.Transport):
+ # pylint: disable=too-many-instance-attributes
"""An XML-RPC transport handling authentication via cookies"""
_http_connection = http.client.HTTPConnection
@@ -34,6 +41,7 @@
def __init__(self, user_agent, credentials=(), cookies=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+ # pylint: disable=protected-access,too-many-arguments
xmlrpc.client.Transport.__init__(self)
self.user_agent = user_agent
self.credentials = credentials
@@ -75,8 +83,8 @@
self.send_content(connection, request_body)
return connection
- # override the send_host hook to also send authentication info
def send_auth(self, connection):
+ """Override the send_host hook to also send authentication info"""
if (self.cookies is not None) and (len(self.cookies) > 0):
for cookie in self.cookies:
connection.putheader('Cookie', '%s=%s' % (cookie.name, cookie.value))
@@ -85,41 +93,46 @@
auth = 'Basic %s' % creds
connection.putheader('Authorization', auth)
- # send content type
- def send_content_type(self, connection):
+ @staticmethod
+ def send_content_type(connection):
+ """Send content type"""
connection.putheader('Content-Type', 'text/xml')
- # send user agent
def send_user_agent(self, connection):
+ """Send user agent"""
connection.putheader('User-Agent', self.user_agent)
- # send custom headers
def send_headers(self, connection, headers):
+ """Send custom headers"""
xmlrpc.client.Transport.send_headers(self, connection, headers)
- for k, v in (self.headers or {}).items():
- connection.putheader(k, v)
+ for key, value in (self.headers or {}).items():
+ connection.putheader(key, value)
- # dummy request class for extracting cookies
class CookieRequest(urllib.request.Request):
- pass
+ """Dummy request class used for extracting cookies"""
- # dummy response info headers helper
class CookieResponseHelper:
+ """Dummy response headers helper"""
+
def __init__(self, response):
self.response = response
def getheaders(self, header):
+ """Get response headers"""
return self.response.msg.getallmatchingheaders(header)
- # dummy response class for extracting cookies
class CookieResponse:
+ """Dummy response class used to extract cookies"""
+
def __init__(self, response):
self.response = response
def info(self):
+ """Get response info from cookies"""
return XMLRPCCookieAuthTransport.CookieResponseHelper(self.response)
def get_response(self, connection, host, handler):
+ """Get server response"""
response = connection.getresponse()
# extract cookies from response headers
if self.cookies is not None:
@@ -143,6 +156,7 @@
def get_client(uri, credentials=(), verbose=False, allow_none=0,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+ # pylint: disable=protected-access,too-many-arguments
"""Get an XML-RPC client which supports basic authentication"""
if uri.startswith('https:'):
transport = SecureXMLRPCCookieAuthTransport(
@@ -156,6 +170,7 @@
def get_client_with_cookies(uri, credentials=(), verbose=False, allow_none=0,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None, cookies=None):
+ # pylint: disable=protected-access,too-many-arguments
"""Get an XML-RPC client which supports authentication through cookies"""
if cookies is None:
cookies = http.cookiejar.CookieJar()
--- a/src/pyams_utils/registry.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/registry.py Tue Nov 26 10:15:05 2019 +0100
@@ -21,7 +21,6 @@
See :ref:`zca` to get a brief introduction about using a local registry with PyAMS packages.
"""
-
import logging
import threading
@@ -52,12 +51,14 @@
_registry = None
def get_registry(self):
+ """Return local registry"""
return self._registry
def set_registry(self, registry):
+ """Define local registry"""
self._registry = registry
-local_registry = LocalRegistry()
+local_registry = LocalRegistry() # pylint: disable=invalid-name
def get_local_registry():
@@ -74,7 +75,7 @@
@subscriber(INewRequest)
-def handle_new_request(event):
+def handle_new_request(event): # pylint: disable=unused-argument
"""New request event subscriber
Is used to initialize local registry to None for any new request
@@ -183,7 +184,7 @@
yield utility
-def get_all_utilities_registered_for(interface):
+def get_all_utilities_registered_for(interface): # pylint: disable=invalid-name
"""Get list of registered utilities for given interface
Do a registry lookup for matching utilities into local registry first, then on each registry
@@ -196,17 +197,17 @@
return result
-class utility_config(object): # pylint: disable=invalid-name
+class utility_config: # 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
:param Interface provides: the interface for which the utility is registered
- Please note that a single utility can be registered several times (using several annotations), with
- different names.
+ Please note that a single utility can be registered several times (using several annotations),
+ with different names.
- If several utilities are registered for the same interface with the same name, the last registered
- utility will override the previous ones.
+ If several utilities are registered for the same interface with the same name, the last
+ registered utility will override the previous ones.
"""
venusian = venusian
@@ -218,13 +219,13 @@
settings = self.__dict__.copy()
depth = settings.pop('_depth', 0)
- def callback(context, name, ob):
- if type(ob) is type:
- factory = ob
+ def callback(context, name, obj):
+ if isinstance(obj, type):
+ factory = obj
component = None
else:
factory = None
- component = ob
+ component = obj
provides = settings.get('provides')
if provides is None:
--- a/src/pyams_utils/scripts/zodb.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/scripts/zodb.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,19 +10,22 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.scripts.zodb module
+This module provides a single function which will be used by "pyams_upgrade" command line
+script to upgrade existing database schema when installing a new application release
+"""
-# import standard library
import argparse
import sys
import textwrap
-# import interfaces
+from pyramid.paster import bootstrap
-# import packages
from pyams_utils.site import site_upgrade
-from pyramid.paster import bootstrap
+
+
+__docformat__ = 'restructuredtext'
def upgrade_site():
@@ -38,7 +41,7 @@
config_uri = args.config_uri
env = bootstrap(config_uri)
- settings, closer = env['registry'].settings, env['closer']
+ closer = env['closer']
try:
site_upgrade(env['request'])
finally:
--- a/src/pyams_utils/site.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/site.py Tue Nov 26 10:15:05 2019 +0100
@@ -11,6 +11,12 @@
#
"""PyAMS_utils.site module
+
+This modules provides classes of elements which can be used as application or site "root"
+objects.
+
+Il also provides functions which are used to manage site's "generations", used to upgrade
+objects while migrating from one version to another.
"""
from persistent.dict import PersistentDict
@@ -48,7 +54,7 @@
BaseSiteRoot defines a basic ACL which gives all permissions to system administrator,
and 'public' permission to everyone. But this ACL is generally overriden in subclasses
- which also inherit from :class:`pyams_security.security.ProtectedObject`.
+ which also inherit from :py:class:`ProtectedObject <pyams_security.security.ProtectedObject>`.
"""
__acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS),
@@ -64,7 +70,9 @@
Gives access to local site manager from */++etc++site* URL
"""
- def traverse(self, name, furtherpath=None):
+ def traverse(self, name, furtherpath=None): # pylint: disable=unused-argument
+ """Traverse to site manager;
+ see :py:class:`ITraversable <zope.traversing.interfaces.ITraversable>`"""
if name == 'site':
return self.context.getSiteManager()
raise NotFound
@@ -96,8 +104,8 @@
factory = request.registry.queryUtility(ISiteRootFactory, default=BaseSiteRoot)
application = root[application_key] = factory()
if IPossibleSite.providedBy(application):
- sm = LocalSiteManager(application, default_folder=False)
- application.setSiteManager(sm)
+ lsm = LocalSiteManager(application, default_folder=False)
+ application.setSiteManager(lsm)
try:
# if some components require a valid and complete registry
# with all registered utilities, they can subscribe to
@@ -106,7 +114,7 @@
get_current_registry().notify(NewLocalSiteCreatedEvent(application))
finally:
hooks.setSite(None)
- import transaction
+ import transaction # pylint: disable=import-outside-toplevel
transaction.commit()
return application
@@ -145,7 +153,7 @@
generations[name] = utility.generation
finally:
hooks.setSite(None)
- import transaction
+ import transaction # pylint: disable=import-outside-toplevel
transaction.commit()
return application
@@ -168,10 +176,10 @@
for interface, name, factory, default_id in utilities:
utility = query_utility(interface, name=name)
if utility is None:
- sm = site.getSiteManager()
- if default_id in sm:
+ lsm = site.getSiteManager()
+ if default_id in lsm:
continue
utility = factory()
registry.notify(ObjectCreatedEvent(utility))
- sm[default_id] = utility
- sm.registerUtility(utility, interface, name=name)
+ lsm[default_id] = utility
+ lsm.registerUtility(utility, interface, name=name)
--- a/src/pyams_utils/tales.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/tales.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,7 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.tales module
+
+This module provides a custom TALES extension engine, which allows you to define custom
+TALES expressions which can be used from Chameleon or Zope templates.
+"""
import re
@@ -21,8 +25,10 @@
from pyams_utils.interfaces.tales import ITALESExtension
+__docformat__ = 'restructuredtext'
-class ContextExprMixin(object):
+
+class ContextExprMixin:
"""Mixin-class for expression compilers"""
transform = None
@@ -36,8 +42,8 @@
return assignment + transform
-FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
-ARGUMENTS_EXPRESSION = re.compile('[^(,)]+')
+FUNCTION_EXPRESSION = re.compile(r'(.+)\((.+)\)', re.MULTILINE | re.DOTALL)
+ARGUMENTS_EXPRESSION = re.compile(r'[^(,)]+')
def render_extension(econtext, name):
@@ -72,7 +78,7 @@
except ValueError:
args = arg.split('.')
result = econtext.get(args.pop(0))
- for arg in args:
+ for arg in args: # pylint: disable=redefined-argument-from-local
result = getattr(result, arg)
return result
else:
--- a/src/pyams_utils/tests/__init__.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/tests/__init__.py Tue Nov 26 10:15:05 2019 +0100
@@ -22,10 +22,10 @@
import sys
-def get_package_dir(input):
+def get_package_dir(value):
"""Get package directory"""
- package_dir = os.path.split(input)[0]
+ package_dir = os.path.split(value)[0]
if package_dir not in sys.path:
sys.path.append(package_dir)
return package_dir
--- a/src/pyams_utils/tests/test_utilsdocs.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/tests/test_utilsdocs.py Tue Nov 26 10:15:05 2019 +0100
@@ -29,7 +29,7 @@
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
-def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):
+def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): # pylint: disable=invalid-name
"""Returns a test suite, based on doctests found in /doctest."""
suite = []
if globs is None:
@@ -61,4 +61,3 @@
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
-
--- a/src/pyams_utils/tests/test_utilsdocstrings.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/tests/test_utilsdocstrings.py Tue Nov 26 10:15:05 2019 +0100
@@ -46,7 +46,7 @@
docs = [doc for doc in docs if not doc.startswith('__')]
for test in docs:
- fd = open(os.path.join(package_dir, test))
+ fd = open(os.path.join(package_dir, test)) # pylint: disable=invalid-name
content = fd.read()
fd.close()
if '>>> ' not in content:
--- a/src/pyams_utils/text.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/text.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,7 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.text module
+
+This module provides text manipulation and conversion functions, as well as a set of TALES
+extensions (see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`).
+"""
import html
@@ -20,7 +24,6 @@
from zope.interface import Interface
from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
-from pyams_utils import _
from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config
from pyams_utils.interfaces.tales import ITALESExtension
from pyams_utils.interfaces.text import IHTMLRenderer
@@ -28,14 +31,20 @@
from pyams_utils.vocabulary import vocabulary_config
-def get_text_start(text, length, max=0):
+__docformat__ = 'restructuredtext'
+
+
+from pyams_utils import _
+
+
+def get_text_start(text, length, maxlen=0):
"""Get first words of given text with maximum given length
If *max* is specified, text is shortened only if remaining text is longer this value
:param str text: initial text
:param integer length: maximum length of resulting text
- :param integer max: if > 0, *text* is shortened only if remaining text is longer than max
+ :param integer maxlen: if > 0, *text* is shortened only if remaining text is longer than max
>>> from pyams_utils.text import get_text_start
>>> get_text_start('This is a long string', 10)
@@ -52,12 +61,13 @@
text_length = len(result)
while (index > 0) and (result[index] != ' '):
index -= 1
- if (index > 0) and (text_length > index + max):
+ if (index > 0) and (text_length > index + maxlen):
return result[:index] + '…'
return text
-@adapter_config(name='truncate', context=(Interface, Interface, Interface), provides=ITALESExtension)
+@adapter_config(name='truncate', context=(Interface, Interface, Interface),
+ provides=ITALESExtension)
class TruncateCharsTalesExtension(ContextRequestViewAdapter):
"""extension:truncate(value, length, max) TALES expression
@@ -67,10 +77,13 @@
"""
@staticmethod
- def render(value, length=50, max=0):
+ def render(value, length=50, maxlen=0):
+ """Render TALES extension;
+ see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
if not value:
return ''
- return get_text_start(value, length, max=max)
+ return get_text_start(value, length, maxlen=maxlen)
@adapter_config(name='raw', context=(str, IRequest), provides=IHTMLRenderer)
@@ -80,7 +93,8 @@
This renderer renders input text 'as is', mainly for use in a <pre> tag.
"""
- def render(self, **kwargs):
+ def render(self, **kwargs): # pylint: disable=unused-argument
+ """Convert raw code as HTML"""
return self.context
@@ -152,8 +166,8 @@
Renderer name can be any registered HTML renderer adapter.
- You can provide several renderers by giving their names separated by semicolon; renderers will then
- act as in a pipe, each renderer transforming output of the previous one.
+ You can provide several renderers by giving their names separated by semicolon; renderers
+ will then act as in a pipe, each renderer transforming output of the previous one.
"""
request = check_request()
registry = request.registry
@@ -164,24 +178,28 @@
return text
-empty_marker = object()
+EMPTY_MARKER = object()
@adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension)
class HTMLTalesExtension(ContextRequestViewAdapter):
"""*extension:html* TALES expression
- If first *context* argument of the renderer is an object for which an :py:class:`IHTMLRenderer`
- adapter can be found, this adapter is used to render the context to HTML; if *context* is a string,
- it is converted to HTML using the renderer defined as second parameter; otherwise, context is just
- converted to string using the :py:func:`str` function.
+ If first *context* argument of the renderer is an object for which an
+ :py:class:`IHTMLRenderer <pyams_utils.interfaces.text.IHTMLRenderer>`
+ adapter can be found, this adapter is used to render the context to HTML; if *context* is a
+ string, it is converted to HTML using the renderer defined as second parameter; otherwise,
+ context is just converted to string using the :py:func:`str` function.
- You can provide several renderers by giving their names separated by semicolon; renderers will then
- act as in a pipe, each renderer transforming output of the previous one.
+ You can provide several renderers by giving their names separated by semicolon; renderers
+ will then act as in a pipe, each renderer transforming output of the previous one.
"""
- def render(self, context=empty_marker, renderer='text'):
- if context is empty_marker:
+ def render(self, context=EMPTY_MARKER, renderer='text'):
+ """Render TALES extension;
+ see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
+ if context is EMPTY_MARKER:
context = self.context
if not context:
return ''
@@ -191,10 +209,9 @@
adapter = registry.queryMultiAdapter((context, self.request), IHTMLRenderer)
if adapter is not None:
return adapter.render()
- elif isinstance(context, str):
+ if isinstance(context, str):
return text_to_html(context, renderer)
- else:
- return str(context)
+ return str(context)
PYAMS_HTML_RENDERERS_VOCABULARY = 'PyAMS HTML renderers'
@@ -204,7 +221,7 @@
class RenderersVocabulary(SimpleVocabulary):
"""Text renderers vocabulary"""
- def __init__(self, context=None):
+ def __init__(self, context=None): # pylint: disable=unused-argument
request = check_request()
registry = request.registry
translate = request.localizer.translate
@@ -226,12 +243,15 @@
@staticmethod
def render(value, css_class='', character='|', start_tag=None, end_tag=None):
+ """Render TALES extension;
+ see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
if not value:
return ''
- br = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '')
+ br_tag = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '')
elements = value.split(character)
if start_tag:
elements[0] = '<{0}>{1}</{0}>'.format(start_tag, elements[0])
if end_tag:
elements[-1] = '<{0}>{1}</{0}>'.format(end_tag, elements[-1])
- return br.join(elements)
+ return br_tag.join(elements)
--- a/src/pyams_utils/timezone/__init__.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/timezone/__init__.py Tue Nov 26 10:15:05 2019 +0100
@@ -39,7 +39,7 @@
@adapter_config(context=IRequest, provides=ITZInfo)
-def tzinfo(request=None):
+def tzinfo(request=None): # pylint: disable=unused-argument
"""request to timezone adapter
There is no easy way to get timezone from a request.
--- a/src/pyams_utils/timezone/utility.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/timezone/utility.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,26 +10,27 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+"""PyAMS_utils.timezone.utility module
-# import interfaces
-from pyams_utils.interfaces.site import ISiteGenerations
-from pyams_utils.interfaces.timezone import IServerTimezone
+"""
-# import packages
from persistent import Persistent
-from pyams_utils.registry import utility_config
-from pyams_utils.site import check_required_utilities
from zope.container.contained import Contained
from zope.interface import implementer
from zope.schema.fieldproperty import FieldProperty
+from pyams_utils.interfaces.site import ISiteGenerations
+from pyams_utils.interfaces.timezone import IServerTimezone
+from pyams_utils.registry import utility_config
+from pyams_utils.site import check_required_utilities
+
+
+__docformat__ = 'restructuredtext'
+
@implementer(IServerTimezone)
class ServerTimezoneUtility(Persistent, Contained):
+ """Server timezone utility"""
timezone = FieldProperty(IServerTimezone['timezone'])
@@ -38,12 +39,13 @@
@utility_config(name='PyAMS timezone', provides=ISiteGenerations)
-class TimezoneGenerationsChecker(object):
+class TimezoneGenerationsChecker:
"""Timezone generations checker"""
order = 10
generation = 1
- def evolve(self, site, current=None):
+ @staticmethod
+ def evolve(site, current=None): # pylint: disable=unused-argument
"""Check for required utilities"""
check_required_utilities(site, REQUIRED_UTILITIES)
--- a/src/pyams_utils/timezone/vocabulary.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/timezone/vocabulary.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,23 +10,24 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.timezone.vocabulary module
+
+This module provides a vocabulary of available timezones
+"""
+
+import pytz
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+
+from pyams_utils.vocabulary import vocabulary_config
-# import standard library
-import pytz
-
-# import interfaces
-
-# import packages
-from pyams_utils.vocabulary import vocabulary_config
-from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+__docformat__ = 'restructuredtext'
@vocabulary_config(name='PyAMS timezones')
class TimezonesVocabulary(SimpleVocabulary):
"""Timezones vocabulary"""
- def __init__(self, *args, **kw):
+ def __init__(self, *args, **kw): # pylint: disable=unused-argument
terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
super(TimezonesVocabulary, self).__init__(terms)
--- a/src/pyams_utils/traversing.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/traversing.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,7 +10,14 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.traversing module
+
+This module provides a custom Pyramid "namespace" traverser: using "++name++" URLs allows
+to traverse URLs based on custom traversing adapters.
+
+It also provides a "get_parent" function, which returns a parent object of given object providing
+a given interface.
+"""
from pyramid.compat import decode_path_info, is_nonstr_iter
from pyramid.exceptions import NotFound, URLDecodeError
@@ -28,6 +35,9 @@
from pyams_utils.registry import query_utility
+__docformat__ = 'restructuredtext'
+
+
class NamespaceTraverser(ResourceTreeTraverser):
"""Custom traverser handling views and namespaces
@@ -41,7 +51,7 @@
NAMESPACE_SELECTOR = PLUS_SELECTOR * 2
def __call__(self, request):
-
+ # pylint: disable=too-many-locals,too-many-branches,too-many-statements
environ = request.environ
matchdict = request.matchdict
@@ -68,8 +78,8 @@
except KeyError:
# if environ['PATH_INFO'] is just not there
path = slash
- except UnicodeDecodeError as e:
- raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
+ except UnicodeDecodeError as exc:
+ raise URLDecodeError(exc.encoding, exc.object, exc.start, exc.end, exc.reason)
if VH_ROOT_KEY in environ:
# HTTP_X_VHM_ROOT
@@ -83,7 +93,7 @@
vroot_idx = -1
root = self.root
- ob = vroot = root
+ obj = vroot = root
request.registry.notify(BeforeTraverseEvent(root, request))
@@ -104,27 +114,28 @@
vpath_tuple = split_path_info(vpath)
for segment in vpath_tuple:
- if ob is not root:
- request.registry.notify(BeforeTraverseEvent(ob, request))
+ if obj is not root:
+ request.registry.notify(BeforeTraverseEvent(obj, request))
if segment == plus_selector:
# check for custom namespace called '+'
- # currently this namespace is used in PyAMS_default_theme package to get direct access to a given
- # content
+ # currently this namespace is used in PyAMS_default_theme package to get
+ # direct access to a given content
registry = get_current_registry()
- traverser = registry.queryMultiAdapter((ob, request), ITraversable, '+')
+ traverser = registry.queryMultiAdapter((obj, request), ITraversable, '+')
if traverser is None:
raise NotFound()
try:
- ob = traverser.traverse(vpath_tuple[vroot_idx + i + 2], vpath_tuple[vroot_idx + i + 3:])
+ obj = traverser.traverse(vpath_tuple[vroot_idx + i + 2],
+ vpath_tuple[vroot_idx + i + 3:])
except IndexError:
- # the "+" namespace traverser is waiting for additional elements from input URL
- # so a "+" URL not followed by something else is just an error!
+ # the "+" namespace traverser is waiting for additional elements from
+ # input URL so a "+" URL not followed by something else is just an error!
raise NotFound()
else:
i += 1
return {
- 'context': ob,
+ 'context': obj,
'view_name': ''.join(vpath_tuple[vroot_idx + i + 2:]),
'subpath': vpath_tuple[i + 2:],
'traversed': vpath_tuple[:vroot_idx + i + 2],
@@ -135,24 +146,25 @@
elif segment[:2] == ns_selector:
# check for namespace prefixed by '++'
- # when a namespace is detected, named "ITraversable" multi-adapters are searched for
- # context and request, or for context, sequentially; a NotFound exception is raised if traverser
- # can't be found, otherwise it's "traverse" method is called to get new context
- ns, name = segment[2:].split(ns_selector, 1)
+ # when a namespace is detected, named "ITraversable" multi-adapters are
+ # searched for context and request, or for context, sequentially; a NotFound
+ # exception is raised if traverser can't be found, otherwise it's "traverse"
+ # method is called to get new context
+ nss, name = segment[2:].split(ns_selector, 1)
registry = get_current_registry()
- traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns)
+ traverser = registry.queryMultiAdapter((obj, request), ITraversable, nss)
if traverser is None:
- traverser = registry.queryAdapter(ob, ITraversable, ns)
+ traverser = registry.queryAdapter(obj, ITraversable, nss)
if traverser is None:
raise NotFound()
- ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
+ obj = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
i += 1
continue
elif segment[:2] == view_selector:
# check for view name prefixed by '@@'
return {
- 'context': ob,
+ 'context': obj,
'view_name': segment[2:],
'subpath': vpath_tuple[i + 1:],
'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -162,10 +174,10 @@
}
try:
- getitem = ob.__getitem__
+ getitem = obj.__getitem__
except AttributeError:
return {
- 'context': ob,
+ 'context': obj,
'view_name': segment,
'subpath': vpath_tuple[i + 1:],
'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -175,10 +187,10 @@
}
try:
- next = getitem(segment)
+ next_item = getitem(segment)
except KeyError:
return {
- 'context': ob,
+ 'context': obj,
'view_name': segment,
'subpath': vpath_tuple[i + 1:],
'traversed': vpath_tuple[:vroot_idx + i + 1],
@@ -187,15 +199,15 @@
'root': root
}
if i == vroot_idx:
- vroot = next
- ob = next
+ vroot = next_item
+ obj = next_item
i += 1
- if ob is not root:
- request.registry.notify(BeforeTraverseEvent(ob, request))
+ if obj is not root:
+ request.registry.notify(BeforeTraverseEvent(obj, request))
return {
- 'context': ob,
+ 'context': obj,
'view_name': empty,
'subpath': subpath,
'traversed': vpath_tuple,
@@ -210,10 +222,10 @@
:param object context: base element
:param Interface interface: the interface that parend should implement
- :param boolean allow_context: if 'True' (the default), traversing is done starting with context; otherwise,
- traversing is done starting from context's parent
- :param callable condition: an optional function that should return a 'True' result when called with parent
- as first argument
+ :param boolean allow_context: if 'True' (the default), traversing is done starting with
+ context; otherwise, traversing is done starting from context's parent
+ :param callable condition: an optional function that should return a 'True' result when
+ called with parent as first argument
"""
if allow_context:
parent = context
@@ -238,6 +250,7 @@
@property
def parents(self):
+ """Get list of parents OIDs"""
intids = query_utility(IIntIds)
if intids is None:
return []
--- a/src/pyams_utils/url.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/url.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,6 +10,19 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_utils.url module
+
+This module provides several functions, adapters and TALES extensions which can be used to
+generate object's URLs.
+
+Three kinds of URLs can be used:
+ - an absolute URL, which is the standard way to access an object via it's physical path
+ - a canonical URL; this URL is the "preferred" one used to access an object, and is typically
+ used by search engines to index contents
+ - a relative URL; some contents can use this kind of URL to get access to an object from another
+ context.
+"""
+
from pyramid.encode import url_quote, urlencode
from pyramid.url import QUERY_SAFE, resource_url
from pyramid_zope_request import PyramidPublisherRequest
@@ -38,18 +51,22 @@
'this-is-my-test'
Single letters are removed from generated URLs:
+
>>> generate_url('This word has a single a')
'this-word-has-single'
But you can define the minimum length of word:
+
>>> generate_url('This word has a single a', min_word_length=4)
'this-word-single'
If input text contains slashes, they are replaced with hyphens:
+
>>> generate_url('This string contains/slash')
'this-string-contains-slash'
Punctation and special characters are completely removed:
+
>>> generate_url('This is a string with a point. And why not?')
'this-is-string-with-point-and-why-not'
"""
@@ -64,6 +81,12 @@
#
def get_display_context(request):
+ """Get current display context
+
+ The display context can be used when we generate a page to display an object in the context
+ of another one; PyAMS_content package is using this feature to display "shared" contents as
+ is they were located inside another site or folder...
+ """
return request.annotations.get(DISPLAY_CONTEXT, request.context)
@@ -99,12 +122,12 @@
else:
result += '/' + view_name
if query:
- qs = ''
+ qstr = ''
if isinstance(query, str):
- qs = '?' + url_quote(query, QUERY_SAFE)
+ qstr = '?' + url_quote(query, QUERY_SAFE)
elif query:
- qs = '?' + urlencode(query, doseq=True)
- result += qs
+ qstr = '?' + urlencode(query, doseq=True)
+ result += qstr
return result
@@ -117,6 +140,9 @@
"""
def render(self, context=None, view_name=None):
+ """Extension rendering; see
+ :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
if context is None:
context = self.context
return absolute_url(context, self.request, view_name)
@@ -127,7 +153,11 @@
#
def canonical_url(context, request, view_name=None, query=None):
- """Get resource canonical URL"""
+ """Get resource canonical URL
+
+ We look for an :py:class:`ICanonicalURL <pyams_utils.interfaces.url.ICanonicalURL>` adapter;
+ if none is found, we use the absolute_url.
+ """
# if we pass a string to canonical_url(), argument is returned as-is!
if isinstance(context, str):
@@ -139,8 +169,7 @@
if url_adapter is not None:
return url_adapter.get_url(view_name, query)
- else:
- return absolute_url(context, request, view_name, query)
+ return absolute_url(context, request, view_name, query)
@adapter_config(name='canonical_url', context=(Interface, Interface, Interface),
@@ -152,6 +181,9 @@
"""
def render(self, context=None, view_name=None):
+ """Render TALES extension; see
+ :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
if context is None:
context = self.context
return canonical_url(context, self.request, view_name)
@@ -166,13 +198,15 @@
"""Default relative URL adapter"""
def get_url(self, display_context=None, view_name=None, query=None):
+ # pylint: disable=unused-argument
+ """Default adapter returns absolute URL"""
return absolute_url(self.context, self.request, view_name, query)
def relative_url(context, request, display_context=None, view_name=None, query=None):
"""Get resource URL relative to given context"""
if isinstance(request, PyramidPublisherRequest):
- request = request._request
+ request = request._request # pylint: disable=protected-access
if display_context is None:
display_context = request.annotations.get(DISPLAY_CONTEXT, request.context)
adapter = request.registry.getMultiAdapter((context, request), IRelativeURL)
@@ -189,6 +223,9 @@
"""
def render(self, context=None, view_name=None, query=None):
+ """Rander TALES extension;
+ see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`
+ """
if context is None:
context = self.context
return relative_url(context, self.request, view_name=view_name, query=query)
--- a/src/pyams_utils/widget/decimal.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/widget/decimal.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,8 +10,10 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_utils.widget.decimal module
+This module provides a custom data converter for decimal fields using dots as separator
+"""
import decimal
@@ -21,6 +23,8 @@
from pyams_utils.adapter import adapter_config
from pyams_utils.schema import IDottedDecimalField
+__docformat__ = 'restructuredtext'
+
from pyams_utils import _
@@ -30,9 +34,6 @@
errorMessage = _('The entered value is not a valid decimal literal.')
- def __init__(self, field, widget):
- super(DottedDecimalDataConverter, self).__init__(field, widget)
-
def toWidgetValue(self, value):
if not value:
return self.field.missing_value
--- a/src/pyams_utils/wsgi.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/wsgi.py Tue Nov 26 10:15:05 2019 +0100
@@ -10,13 +10,12 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
-# import standard library
+"""PyAMS_utils.wsgi module
-# import interfaces
+This module provides a method decorator which can store it's value into request environment
+"""
-# import packages
+__docformat__ = 'restructuredtext'
def wsgi_environ_cache(*names):
@@ -25,17 +24,20 @@
:param [string...] names: keys to cache into environ; len(names) must
be equal to the result's length or scalar
"""
- def decorator(fn):
+
+ def decorator(func):
+
def function_wrapper(self, request):
scalar = len(names) == 1
try:
- rs = [request.environ[cached_key] for cached_key in names]
+ env = [request.environ[cached_key] for cached_key in names]
except KeyError:
- rs = fn(self, request)
+ env = func(self, request)
if scalar:
- rs = [rs, ]
- request.environ.update(zip(names, rs))
- return rs[0] if scalar else rs
+ env = [env, ]
+ request.environ.update(zip(names, env))
+ return env[0] if scalar else env
+
return function_wrapper
return decorator
--- a/src/pyams_utils/zodb.py Fri Nov 22 18:57:44 2019 +0100
+++ b/src/pyams_utils/zodb.py Tue Nov 26 10:15:05 2019 +0100
@@ -83,7 +83,8 @@
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.
+ :py:class:`ZODBConnection <pyams_utils.zodb.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:
@@ -147,15 +148,15 @@
:return: tuple containing ZEO client storage and DB object (if *get_storage* argument is
set to *True*), or only DB object otherwise
"""
- db = DB((self.server_name, self.server_port),
- storage=self.storage,
- username=self.username or '',
- password=self.password or '',
- realm=self.server_realm,
- blob_dir=self.blob_dir,
- shared_blob_dir=self.shared_blob_dir,
- wait_timeout=wait_timeout)
- return (db.storage, db) if get_storage else db
+ zdb = DB((self.server_name, self.server_port),
+ storage=self.storage,
+ username=self.username or '',
+ password=self.password or '',
+ realm=self.server_realm,
+ blob_dir=self.blob_dir,
+ shared_blob_dir=self.shared_blob_dir,
+ wait_timeout=wait_timeout)
+ return (zdb.storage, zdb) if get_storage else zdb
@property
def connection(self):
@@ -209,8 +210,8 @@
if settings is None:
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()
+ zdb = db_from_uri(uri, name, {})
+ return zdb.open()
class ZODBConnection:
@@ -248,7 +249,7 @@
return self._connection
@property
- def db(self):
+ def db(self): # pylint: disable=invalid-name
"""Database getter"""
return self._db
@@ -261,8 +262,8 @@
"""Load named connection matching registry settings"""
for name, uri in get_uris(self.settings):
if name == self.name:
- db = db_from_uri(uri, name, {})
- connection = self._connection = db.open()
+ zdb = db_from_uri(uri, name, {})
+ connection = self._connection = zdb.open()
self._db = connection.db()
self._storage = self.db.storage
return connection
--- a/tox.ini Fri Nov 22 18:57:44 2019 +0100
+++ b/tox.ini Tue Nov 26 10:15:05 2019 +0100
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = py34, py35, py36, pypy
+envlist = py35, py36, py37, pypy
[testenv]
commands = python setup.py test