# HG changeset patch # User FLORAC Thierry # Date 1574517444 -3600 # Node ID 63284c98cdc1b9bb7a781984eec6d7d40bbe8a05 # Parent 2022e4da3ad9cf1507b699108210aa2cb9e2cf11 Updated buildout diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/adapter.py --- a/src/pyams_utils/adapter.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/adapter.py Sat Nov 23 14:57:24 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' diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/context.py --- a/src/pyams_utils/context.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/context.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/decorator.py --- a/src/pyams_utils/decorator.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/decorator.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/include.py --- a/src/pyams_utils/include.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/include.py Sat Nov 23 14:57:24 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: diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/__init__.py --- a/src/pyams_utils/interfaces/__init__.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/__init__.py Sat Nov 23 14:57:24 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. """ diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/data.py --- a/src/pyams_utils/interfaces/data.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/data.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/inherit.py --- a/src/pyams_utils/interfaces/inherit.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/inherit.py Sat Nov 23 14:57:24 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 ` +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 _ diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/intids.py --- a/src/pyams_utils/interfaces/intids.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/intids.py Sat Nov 23 14:57:24 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) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/site.py --- a/src/pyams_utils/interfaces/site.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/site.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/size.py --- a/src/pyams_utils/interfaces/size.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/size.py Sat Nov 23 14:57:24 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""" diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/tales.py --- a/src/pyams_utils/interfaces/tales.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/tales.py Sat Nov 23 14:57:24 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): diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/text.py --- a/src/pyams_utils/interfaces/text.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/text.py Sat Nov 23 14:57:24 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 _ diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/timezone.py --- a/src/pyams_utils/interfaces/timezone.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/timezone.py Sat Nov 23 14:57:24 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 _ diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/traversing.py --- a/src/pyams_utils/interfaces/traversing.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/traversing.py Sat Nov 23 14:57:24 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): diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/tree.py --- a/src/pyams_utils/interfaces/tree.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/tree.py Sat Nov 23 14:57:24 2019 +0100 @@ -1,7 +1,5 @@ -### -*- coding: utf-8 -*- #################################################### -############################################################################## # -# Copyright (c) 2012 Thierry Florac +# Copyright (c) 2008-2015 Thierry Florac # 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/url.py --- a/src/pyams_utils/interfaces/url.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/url.py Sat Nov 23 14:57:24 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 ` for a longer description. +""" from zope.interface import Interface +__docformat__ = 'restructuredtext' + + class ICanonicalURL(Interface): """Interface used to get content's canonical URL""" diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/interfaces/zeo.py --- a/src/pyams_utils/interfaces/zeo.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/interfaces/zeo.py Sat Nov 23 14:57:24 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) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/protocol/http.py --- a/src/pyams_utils/protocol/http.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/protocol/http.py Sat Nov 23 14:57:24 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, diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/protocol/tcp.py --- a/src/pyams_utils/protocol/tcp.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/protocol/tcp.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/protocol/xmlrpc.py --- a/src/pyams_utils/protocol/xmlrpc.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/protocol/xmlrpc.py Sat Nov 23 14:57:24 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() diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/registry.py --- a/src/pyams_utils/registry.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/registry.py Sat Nov 23 14:57:24 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: diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/scripts/zodb.py --- a/src/pyams_utils/scripts/zodb.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/scripts/zodb.py Sat Nov 23 14:57:24 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: diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/site.py --- a/src/pyams_utils/site.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/site.py Sat Nov 23 14:57:24 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 `. """ __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 `""" 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) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/tales.py --- a/src/pyams_utils/tales.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/tales.py Sat Nov 23 14:57:24 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: diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/tests/__init__.py --- a/src/pyams_utils/tests/__init__.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/tests/__init__.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/tests/test_utilsdocs.py --- a/src/pyams_utils/tests/test_utilsdocs.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/tests/test_utilsdocs.py Sat Nov 23 14:57:24 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') - diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/tests/test_utilsdocstrings.py --- a/src/pyams_utils/tests/test_utilsdocstrings.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/tests/test_utilsdocstrings.py Sat Nov 23 14:57:24 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: diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/text.py --- a/src/pyams_utils/text.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/text.py Sat Nov 23 14:57:24 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 `). +""" 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 ` + """ 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
 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 `
+    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 `
+        """
+        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 `
+        """
         if not value:
             return ''
-        br = '
'.format('class="{0}"'.format(css_class) if css_class else '') + br_tag = '
'.format('class="{0}"'.format(css_class) if css_class else '') elements = value.split(character) if start_tag: elements[0] = '<{0}>{1}'.format(start_tag, elements[0]) if end_tag: elements[-1] = '<{0}>{1}'.format(end_tag, elements[-1]) - return br.join(elements) + return br_tag.join(elements) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/timezone/__init__.py --- a/src/pyams_utils/timezone/__init__.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/timezone/__init__.py Sat Nov 23 14:57:24 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. diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/timezone/utility.py --- a/src/pyams_utils/timezone/utility.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/timezone/utility.py Sat Nov 23 14:57:24 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) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/timezone/vocabulary.py --- a/src/pyams_utils/timezone/vocabulary.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/timezone/vocabulary.py Sat Nov 23 14:57:24 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) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/traversing.py --- a/src/pyams_utils/traversing.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/traversing.py Sat Nov 23 14:57:24 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 [] diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/url.py --- a/src/pyams_utils/url.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/url.py Sat Nov 23 14:57:24 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 ` + """ 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 ` 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 ` + """ 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 ` + """ if context is None: context = self.context return relative_url(context, self.request, view_name=view_name, query=query) diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/widget/decimal.py --- a/src/pyams_utils/widget/decimal.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/widget/decimal.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/wsgi.py --- a/src/pyams_utils/wsgi.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/wsgi.py Sat Nov 23 14:57:24 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 diff -r 2022e4da3ad9 -r 63284c98cdc1 src/pyams_utils/zodb.py --- a/src/pyams_utils/zodb.py Sat Nov 23 01:24:11 2019 +0100 +++ b/src/pyams_utils/zodb.py Sat Nov 23 14:57:24 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 ` 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