# HG changeset patch # User Philippe Pepiot # Date 1558708154 -7200 # Node ID 32ee89340e590e07edd20c6811742f8e25c6dfdf # Parent 24b9073a661776e6636e4d23621c251c8dfb097c# Parent ba5231e1aa45614706e8675a5822eb874d230923 Merge 3.26 diff -r ba5231e1aa45 -r 32ee89340e59 MANIFEST.in --- a/MANIFEST.in Tue May 21 10:50:08 2019 +0200 +++ b/MANIFEST.in Fri May 24 16:29:14 2019 +0200 @@ -50,8 +50,6 @@ recursive-include cubicweb/devtools/test/data *.py *.txt *.js *.po.ref recursive-include cubicweb/entities/test *.py recursive-include cubicweb/entities/test/data *.py -recursive-include cubicweb/etwist/test *.py -recursive-include cubicweb/etwist/test/data *.py recursive-include cubicweb/ext/test *.py recursive-include cubicweb/ext/test/data *.py recursive-include cubicweb/hooks/test *.py @@ -68,11 +66,14 @@ recursive-include cubicweb/server/test/data-schemaserial *.py include cubicweb/web/test/testutils.js recursive-include cubicweb/web/test *.py +include cubicweb/web/test/data/cubicweb_file/data/file.png +include cubicweb/web/test/data/cubicweb_file/wdoc/toc.xml recursive-include cubicweb/web/test/data bootstrap_cubes pouet.css *.py recursive-include cubicweb/web/test/data/static/jstests *.js *.html *.json recursive-include cubicweb/wsgi/test *.py include cubicweb/pyramid/development.ini.tmpl +include cubicweb/pyramid/pyramid.ini.tmpl include cubicweb/web/data/jquery-treeview/*.md diff -r ba5231e1aa45 -r 32ee89340e59 README --- a/README Tue May 21 10:50:08 2019 +0200 +++ b/README Fri May 24 16:29:14 2019 +0200 @@ -21,9 +21,14 @@ Execute:: - apt-get install cubicweb cubicweb-dev cubicweb-blog + python3 -m venv venv + source venv/bin/activate + pip install 'cubicweb[pyramid]' cubicweb-blog cubicweb-ctl create blog myblog - cubicweb-ctl start -D myblog + # read how to create your ~/etc/cubicweb.d/myblog/pyramid.ini file here: + # https://cubicweb.readthedocs.io/en/latest/book/pyramid/settings/#pyramid-settings-file + # then start your instance: + cubicweb-ctl pyramid -D myblog sensible-browser http://localhost:8080/ Details at https://cubicweb.readthedocs.io/en/3.26/tutorials/base/blog-in-five-minutes @@ -31,6 +36,15 @@ You can also look at the latest builds on Logilab's jenkins: https://jenkins.logilab.org/ +Test +---- + +Simply run the `tox` command in the root folder of this repository: + + tox + +How to install tox: https://tox.readthedocs.io/en/latest/install.html + Documentation ------------- diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb.spec --- a/cubicweb.spec Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb.spec Fri May 24 16:29:14 2019 +0200 @@ -21,7 +21,6 @@ BuildArch: noarch Requires: %{python} -Requires: %{python}-six >= 1.4.0 Requires: %{python}-logilab-common >= 1.4.0 Requires: %{python}-logilab-mtconverter >= 0.8.0 Requires: %{python}-rql >= 0.34.0 @@ -29,8 +28,6 @@ Requires: %{python}-logilab-database >= 1.15.0 Requires: %{python}-passlib Requires: %{python}-lxml -Requires: %{python}-unittest2 >= 0.7.0 -Requires: %{python}-twisted-web < 16.0.0 Requires: %{python}-markdown Requires: pytz # the schema view uses `dot'; at least on el5, png output requires graphviz-gd diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/__init__.py --- a/cubicweb/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -20,19 +20,13 @@ """ -import imp import logging import os import pickle import sys -import types import warnings import zlib -from six import PY2, binary_type, text_type -from six.moves import builtins - -from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods from yams.constraints import BASE_CONVERTERS, BASE_CHECKERS from yams.schema import role_name as rname @@ -44,11 +38,7 @@ from yams import ValidationError from cubicweb._exceptions import * # noqa -if PY2: - # http://bugs.python.org/issue10211 - from StringIO import StringIO as BytesIO -else: - from io import BytesIO +from io import BytesIO # ignore the pygments UserWarnings warnings.filterwarnings('ignore', category=UserWarning, @@ -67,20 +57,12 @@ # '_' is available to mark internationalized string but should not be used to # do the actual translation -_ = text_type -if not hasattr(builtins, '_'): - builtins._ = deprecated("[3.22] Use 'from cubicweb import _'")(_) - - -# convert eid to the right type, raise ValueError if it's not a valid eid -@deprecated('[3.17] typed_eid() was removed. replace it with int() when needed.') -def typed_eid(eid): - return int(eid) +_ = str class Binary(BytesIO): """class to hold binary data. Use BytesIO to prevent use of unicode data""" - _allowed_types = (binary_type, bytearray, buffer if PY2 else memoryview) # noqa: F405 + _allowed_types = (bytes, bytearray, memoryview) def __init__(self, buf=b''): assert isinstance(buf, self._allowed_types), \ @@ -156,7 +138,7 @@ def check_password(eschema, value): - return isinstance(value, (binary_type, Binary)) + return isinstance(value, (bytes, Binary)) BASE_CHECKERS['Password'] = check_password @@ -165,7 +147,7 @@ def str_or_binary(value): if isinstance(value, Binary): return value - return binary_type(value) + return bytes(value) BASE_CONVERTERS['Password'] = str_or_binary @@ -278,60 +260,3 @@ not be processed, a memory allocation error occurred during processing, etc. """ - - -# Import hook for "legacy" cubes ############################################## - -class _CubesLoader(object): - - def __init__(self, *modinfo): - self.modinfo = modinfo - - def load_module(self, fullname): - try: - # If there is an existing module object named 'fullname' in - # sys.modules , the loader must use that existing module. - # Otherwise, the reload() builtin will not work correctly. - return sys.modules[fullname] - except KeyError: - pass - if fullname == 'cubes': - mod = sys.modules[fullname] = types.ModuleType( - fullname, doc='CubicWeb cubes') - else: - modname, file, pathname, description = self.modinfo - try: - mod = sys.modules[fullname] = imp.load_module( - modname, file, pathname, description) - finally: - # https://docs.python.org/2/library/imp.html#imp.load_module - # Important: the caller is responsible for closing the file - # argument, if it was not None, even when an exception is - # raised. This is best done using a try ... finally statement - if file is not None: - file.close() - return mod - - -class _CubesImporter(object): - """Module finder handling redirection of import of "cubes." - to "cubicweb_". - """ - - @classmethod - def install(cls): - if not any(isinstance(x, cls) for x in sys.meta_path): - self = cls() - sys.meta_path.append(self) - - def find_module(self, fullname, path=None): - if fullname == 'cubes': - return _CubesLoader() - elif fullname.startswith('cubes.') and fullname.count('.') == 1: - modname = 'cubicweb_' + fullname.split('.', 1)[1] - try: - modinfo = imp.find_module(modname) - except ImportError: - return None - else: - return _CubesLoader(modname, *modinfo) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/__pkginfo__.py --- a/cubicweb/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,8 @@ modname = distname = "cubicweb" -numversion = (3, 26, 10) -version = '.'.join(str(num) for num in numversion) +numversion = (3, 27, 0) +version = '.'.join(str(num) for num in numversion) + '.dev0' description = "a repository of entities / relations for knowledge management" author = "Logilab" @@ -34,7 +34,7 @@ classifiers = [ 'Environment :: Web Environment', 'Framework :: CubicWeb', - 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Programming Language :: JavaScript', ] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/_exceptions.py --- a/cubicweb/_exceptions.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/_exceptions.py Fri May 24 16:29:14 2019 +0200 @@ -18,75 +18,78 @@ """Exceptions shared by different cubicweb packages.""" - -from warnings import warn +from logilab.common.decorators import cachedproperty +from logilab.common.registry import RegistryException -from six import PY2, text_type - -from logilab.common.decorators import cachedproperty - -from yams import ValidationError +from yams import ValidationError # noqa: F401 # abstract exceptions ######################################################### + class CubicWebException(Exception): """base class for cubicweb server exception""" msg = "" - def __unicode__(self): + + def __str__(self): if self.msg: if self.args: return self.msg % tuple(self.args) else: return self.msg else: - return u' '.join(text_type(arg) for arg in self.args) - - def __str__(self): - res = self.__unicode__() - if PY2: - res = res.encode('utf-8') - return res + return u' '.join(str(arg) for arg in self.args) class ConfigurationError(CubicWebException): """a misconfiguration error""" + class InternalError(CubicWebException): """base class for exceptions which should not occur""" + class SecurityError(CubicWebException): """base class for cubicweb server security exceptions""" + class RepositoryError(CubicWebException): """base class for repository exceptions""" + class SourceException(CubicWebException): """base class for source exceptions""" + class CubicWebRuntimeError(CubicWebException): """base class for runtime exceptions""" # repository exceptions ####################################################### + class ConnectionError(RepositoryError): """raised when a bad connection id is given or when an attempt to establish a connection failed """ + class AuthenticationError(ConnectionError): """raised when an attempt to establish a connection failed due to wrong connection information (login / password or other authentication token) """ + class BadConnectionId(ConnectionError): """raised when a bad connection id is given""" + class UnknownEid(RepositoryError): """the eid is not defined in the system tables""" msg = 'No entity with eid %s in the repository' + class UniqueTogetherError(RepositoryError): """raised when a unique_together constraint caused an IntegrityError""" + def __init__(self, session, **kwargs): self.session = session assert 'rtypes' in kwargs or 'cstrname' in kwargs @@ -98,23 +101,20 @@ def rtypes(self): if 'rtypes' in self.kwargs: return self.kwargs['rtypes'] - cstrname = text_type(self.kwargs['cstrname']) + cstrname = str(self.kwargs['cstrname']) cstr = self.session.find('CWUniqueTogetherConstraint', name=cstrname).one() return sorted(rtype.name for rtype in cstr.relations) - @cachedproperty - def args(self): - warn('[3.18] UniqueTogetherError.args is deprecated, just use ' - 'the .rtypes accessor.', - DeprecationWarning) - # the first argument, etype, is never used and was never garanteed anyway - return None, self.rtypes - class ViolatedConstraint(RepositoryError): - def __init__(self, cnx, cstrname): + def __init__(self, cnx, cstrname, query): self.cnx = cnx self.cstrname = cstrname + message = ( + "constraint '%s' is being violated by the query '%s'. " + "You can run the inverted constraint on the database to list the problematic rows." + ) % (cstrname, query) + super(ViolatedConstraint, self).__init__(message) # security exceptions ######################################################### @@ -127,7 +127,7 @@ msg1 = u'You are not allowed to perform %s operation on %s' var = None - def __unicode__(self): + def __str__(self): try: if self.args and len(self.args) == 2: return self.msg1 % self.args @@ -135,7 +135,8 @@ return u' '.join(self.args) return self.msg except Exception as ex: - return text_type(ex) + return str(ex) + class Forbidden(SecurityError): """raised when a user tries to perform a forbidden action @@ -143,6 +144,7 @@ # source exceptions ########################################################### + class EidNotInSource(SourceException): """trying to access an object with a particular eid from a particular source has failed @@ -152,31 +154,33 @@ # registry exceptions ######################################################### -# pre 3.15 bw compat -from logilab.common.registry import RegistryException, ObjectNotFound, NoSelectableObject - class UnknownProperty(RegistryException): """property found in database but unknown in registry""" # query exception ############################################################# + class QueryError(CubicWebRuntimeError): """a query try to do something it shouldn't""" + class NotAnEntity(CubicWebRuntimeError): """raised when get_entity is called for a column which doesn't contain a non final entity """ + class MultipleResultsError(CubicWebRuntimeError): """raised when ResultSet.one() is called on a resultset with multiple rows of multiple columns. """ + class NoResultError(CubicWebRuntimeError): """raised when no result is found but at least one is expected. """ + class UndoTransactionException(QueryError): """Raised when undoing a transaction could not be performed completely. @@ -208,8 +212,10 @@ # tools exceptions ############################################################ + class ExecutionError(Exception): """server execution control error (already started, not running...)""" + # pylint: disable=W0611 -from logilab.common.clcommands import BadCommandUsage +from logilab.common.clcommands import BadCommandUsage # noqa: E402, F401 diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/_gcdebug.py --- a/cubicweb/_gcdebug.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/_gcdebug.py Fri May 24 16:29:14 2019 +0200 @@ -15,12 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from __future__ import print_function - import gc, types, weakref -from six import PY2 - from cubicweb.schema import CubicWebRelationSchema, CubicWebEntitySchema try: from cubicweb.web.request import _NeedAuthAccessMock @@ -37,9 +33,6 @@ types.ModuleType, types.FunctionType, types.MethodType, types.MemberDescriptorType, types.GetSetDescriptorType, ) -if PY2: - # weakref.WeakKeyDictionary fails isinstance check on Python 3.5. - IGNORE_CLASSES += (weakref.WeakKeyDictionary, ) if _NeedAuthAccessMock is not None: IGNORE_CLASSES = IGNORE_CLASSES + (_NeedAuthAccessMock,) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/appobject.py --- a/cubicweb/appobject.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/appobject.py Fri May 24 16:29:14 2019 +0200 @@ -31,26 +31,10 @@ from logging import getLogger -from logilab.common.deprecation import deprecated, class_renamed from logilab.common.logging_ext import set_log_methods -# first line imports for bw compat -from logilab.common.registry import (objectify_predicate, traced_selection, Predicate, - RegistrableObject, yes) - +from logilab.common.registry import RegistrableObject, yes -objectify_selector = deprecated('[3.15] objectify_selector has been ' - 'renamed to objectify_predicates in ' - 'logilab.common.registry')(objectify_predicate) -traced_selection = deprecated('[3.15] traced_selection has been ' - 'moved to logilab.common.registry')(traced_selection) -Selector = class_renamed('Selector', Predicate, - '[3.15] Selector has been renamed to Predicate ' - 'in logilab.common.registry') - -@deprecated('[3.15] lltrace decorator can now be removed') -def lltrace(func): - return func # the base class for all appobjects ############################################ @@ -156,6 +140,3 @@ info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None set_log_methods(AppObject, getLogger('cubicweb.appobject')) - -# defined here to avoid warning on usage on the AppObject class -yes = deprecated('[3.15] yes has been moved to logilab.common.registry')(yes) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/crypto.py --- a/cubicweb/crypto.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/crypto.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,7 @@ from base64 import b64encode, b64decode - -from six.moves import cPickle as pickle +import pickle from Crypto.Cipher import Blowfish @@ -29,6 +28,8 @@ def _cypherer(seed): + if isinstance(seed, str): + seed = seed.encode('utf-8') try: return _CYPHERERS[seed] except KeyError: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/cwconfig.py --- a/cubicweb/cwconfig.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/cwconfig.py Fri May 24 16:29:14 2019 +0200 @@ -108,10 +108,6 @@ `` is the source checkout's ``cubicweb`` directory: -* main cubes directory is `/../../cubes`. You can specify - another one with :envvar:`CW_INSTANCES_DIR` environment variable or simply - add some other directories by using :envvar:`CW_CUBES_PATH` - * cubicweb migration files are searched in `/misc/migration` instead of `/share/cubicweb/migration/`. @@ -157,11 +153,6 @@ Resource mode: user or system, as explained in :ref:`ResourceMode`. -.. envvar:: CW_CUBES_PATH - - Augments the default search path for cubes. You may specify several - directories using ':' as separator (';' under windows environment). - .. envvar:: CW_INSTANCES_DIR Directory where cubicweb instances will be found. @@ -175,27 +166,21 @@ Directory where pid files will be written """ -from __future__ import print_function - import importlib import logging import logging.config import os -from os.path import (exists, join, expanduser, abspath, normpath, - basename, isdir, dirname, splitext, realpath) +from os.path import (exists, join, expanduser, abspath, + basename, dirname, splitext, realpath) import pkgutil import pkg_resources -import re from smtplib import SMTP import stat import sys from threading import Lock from warnings import filterwarnings -from six import text_type - -from logilab.common.decorators import cached, classproperty -from logilab.common.deprecation import deprecated +from logilab.common.decorators import cached from logilab.common.logging_ext import set_log_methods, init_log from logilab.common.configuration import (Configuration, Method, ConfigurationMixIn, merge_options, @@ -220,7 +205,7 @@ def possible_configurations(directory): """return a list of installed configurations in a directory - according to \*-ctl files + according to *-ctl files """ return [name for name in ('repository', 'all-in-one', 'pyramid') if exists(join(directory, '%s.conf' % name))] @@ -243,15 +228,6 @@ return cube -def _cube_modname(cube): - modname = _cube_pkgname(cube) - loader = pkgutil.find_loader(modname) - if loader: - return modname - else: - return 'cubes.' + cube - - def _expand_modname(modname, recursive=True): """expand modules names `modname` if exists by recursively walking submodules and subpackages and yield (submodname, filepath) including @@ -382,11 +358,6 @@ mode = os.environ.get('CW_MODE', 'system') assert mode in ('system', 'user'), '"CW_MODE" should be either "user" or "system"' - _CUBES_DIR = join(_INSTALL_PREFIX, 'share', 'cubicweb', 'cubes') - assert _CUBES_DIR # XXX only meaningful if CW_CUBES_DIR is not set - CUBES_DIR = realpath(abspath(os.environ.get('CW_CUBES_DIR', _CUBES_DIR))) - CUBES_PATH = os.environ.get('CW_CUBES_PATH', '').split(os.pathsep) - options = ( ('log-threshold', {'type' : 'string', # XXX use a dedicated type? @@ -479,23 +450,6 @@ entry_point) continue cubes.add(modname) - # Legacy cubes. - for directory in cls.cubes_search_path(): - if not exists(directory): - cls.error('unexistant directory in cubes search path: %s' - % directory) - continue - for cube in os.listdir(directory): - if cube == 'shared': - continue - if not re.match('[_A-Za-z][_A-Za-z0-9]*$', cube): - continue # skip invalid python package name - if cube == 'pyramid': - cls._warn_pyramid_cube() - continue - cubedir = join(directory, cube) - if isdir(cubedir) and exists(join(cubedir, '__init__.py')): - cubes.add(cube) def sortkey(cube): """Preserve sorting with "cubicweb_" prefix.""" @@ -510,23 +464,6 @@ return sorted(cubes, key=sortkey) @classmethod - def cubes_search_path(cls): - """return the path of directories where cubes should be searched""" - path = [realpath(abspath(normpath(directory))) for directory in cls.CUBES_PATH - if directory.strip() and exists(directory.strip())] - if not cls.CUBES_DIR in path and exists(cls.CUBES_DIR): - path.append(cls.CUBES_DIR) - return path - - @classproperty - def extrapath(cls): - extrapath = {} - for cubesdir in cls.cubes_search_path(): - if cubesdir != cls.CUBES_DIR: - extrapath[cubesdir] = 'cubes' - return extrapath - - @classmethod def cube_dir(cls, cube): """return the cube directory for the given cube id, raise `ConfigurationError` if it doesn't exist @@ -535,15 +472,9 @@ loader = pkgutil.find_loader(pkgname) if loader: return dirname(loader.get_filename()) - # Legacy cubes. - for directory in cls.cubes_search_path(): - cubedir = join(directory, cube) - if exists(cubedir): - return cubedir msg = 'no module %(pkg)s in search path nor cube %(cube)r in %(path)s' raise ConfigurationError(msg % {'cube': cube, - 'pkg': _cube_pkgname(cube), - 'path': cls.cubes_search_path()}) + 'pkg': _cube_pkgname(cube)}) @classmethod def cube_migration_scripts_dir(cls, cube): @@ -553,18 +484,9 @@ @classmethod def cube_pkginfo(cls, cube): """return the information module for the given cube""" + cube = CW_MIGRATION_MAP.get(cube, cube) pkgname = _cube_pkgname(cube) - try: - return importlib.import_module('%s.__pkginfo__' % pkgname) - except ImportError: - cube = CW_MIGRATION_MAP.get(cube, cube) - try: - parent = __import__('cubes.%s.__pkginfo__' % cube) - return getattr(parent, cube).__pkginfo__ - except Exception as ex: - raise ConfigurationError( - 'unable to find packaging information for cube %s (%s: %s)' - % (cube, ex.__class__.__name__, ex)) + return importlib.import_module('%s.__pkginfo__' % pkgname) @classmethod def cube_version(cls, cube): @@ -662,16 +584,8 @@ raise ConfigurationError(ex) @classmethod - def cls_adjust_sys_path(cls): - """update python path if necessary""" - from cubicweb import _CubesImporter - _CubesImporter.install() - import cubes - cubes.__path__ = cls.cubes_search_path() - - @classmethod def load_available_configs(cls): - for confmod in ('web.webconfig', 'etwist.twconfig', + for confmod in ('web.webconfig', 'server.serverconfig', 'pyramid.config'): try: __import__('cubicweb.%s' % confmod) @@ -681,8 +595,7 @@ @classmethod def load_cwctl_plugins(cls): - cls.cls_adjust_sys_path() - for ctlmod in ('web.webctl', 'etwist.twctl', 'server.serverctl', + for ctlmod in ('web.webctl', 'server.serverctl', 'devtools.devctl', 'pyramid.pyramidctl'): try: __import__('cubicweb.%s' % ctlmod) @@ -695,10 +608,7 @@ cubedir = cls.cube_dir(cube) pluginfile = join(cubedir, 'ccplugin.py') initfile = join(cubedir, '__init__.py') - if cube.startswith('cubicweb_'): - pkgname = cube - else: - pkgname = 'cubes.%s' % cube + pkgname = _cube_pkgname(cube) if exists(pluginfile): try: __import__(pkgname + '.ccplugin') @@ -727,7 +637,7 @@ self.adjust_sys_path() self.load_defaults() # will be properly initialized later by _gettext_init - self.translations = {'en': (text_type, lambda ctx, msgid: text_type(msgid) )} + self.translations = {'en': (str, lambda ctx, msgid: str(msgid) )} self._site_loaded = set() # don't register ReStructured Text directives by simple import, avoid pb # with eg sphinx. @@ -741,7 +651,7 @@ def adjust_sys_path(self): # overriden in CubicWebConfiguration - self.cls_adjust_sys_path() + pass def init_log(self, logthreshold=None, logfile=None, syslog=False): """init the log service""" @@ -769,7 +679,7 @@ modnames.append(('cubicweb', 'cubicweb.schemas.' + name)) for cube in reversed(self.cubes()): for modname, filepath in _expand_modname( - '{0}.schema'.format(_cube_modname(cube)), + '{0}.schema'.format(_cube_pkgname(cube)), recursive=False): modnames.append((cube, modname)) if self.apphome: @@ -805,12 +715,8 @@ def _load_site_cubicweb(self, cube): """Load site_cubicweb.py from `cube` (or apphome if cube is None).""" if cube is not None: - try: - modname = 'cubicweb_%s' % cube - __import__(modname) - except ImportError: - modname = 'cubes.%s' % cube - __import__(modname) + modname = _cube_pkgname(cube) + __import__(modname) modname = modname + '.site_cubicweb' __import__(modname) return sys.modules[modname] @@ -864,11 +770,7 @@ self._cubes = self.reorder_cubes(cubes) # load cubes'__init__.py file first for cube in cubes: - try: - importlib.import_module(_cube_pkgname(cube)) - except ImportError: - # Legacy cube. - __import__('cubes.%s' % cube) + importlib.import_module(_cube_pkgname(cube)) self.load_site_cubicweb() def cubes(self): @@ -1084,7 +986,7 @@ # set to true while creating an instance self.creating = creating super(CubicWebConfiguration, self).__init__(debugmode) - fake_gettext = (text_type, lambda ctx, msgid: text_type(msgid)) + fake_gettext = (str, lambda ctx, msgid: str(msgid)) for lang in self.available_languages(): self.translations[lang] = fake_gettext self._cubes = None @@ -1247,7 +1149,7 @@ """return available translation for an instance, by looking for compiled catalog - take \*args to be usable as a vocabulary method + take *args to be usable as a vocabulary method """ from glob import glob yield 'en' # ensure 'en' is yielded even if no .mo found @@ -1287,7 +1189,7 @@ def appobjects_cube_modnames(self, cube): modnames = [] - cube_modname = _cube_modname(cube) + cube_modname = _cube_pkgname(cube) cube_submodnames = self._sorted_appobjects(self.cube_appobject_path) for name in cube_submodnames: for modname, filepath in _expand_modname('.'.join([cube_modname, name])): @@ -1358,7 +1260,6 @@ # alias to get a configuration instance from an instance id instance_configuration = CubicWebConfiguration.config_for -application_configuration = deprecated('use instance_configuration')(instance_configuration) _EXT_REGISTERED = False diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/cwctl.py --- a/cubicweb/cwctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/cwctl.py Fri May 24 16:29:14 2019 +0200 @@ -18,15 +18,13 @@ """the cubicweb-ctl tool, based on logilab.common.clcommands to provide a pluggable commands system. """ -from __future__ import print_function - # *ctl module should limit the number of import to be imported as quickly as # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash # completion). So import locally in command helpers. import sys -from warnings import warn, filterwarnings -from os import remove, listdir, system, pathsep -from os.path import exists, join, isdir, dirname, abspath +from warnings import filterwarnings +from os import listdir, system +from os.path import exists, join, isdir try: from os import kill, getpgid @@ -36,8 +34,6 @@ def getpgid(): """win32 getpgid implementation""" -from six.moves.urllib.parse import urlparse - from logilab.common.clcommands import CommandLine from logilab.common.shellutils import ASK from logilab.common.configuration import merge_options @@ -46,11 +42,11 @@ from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS from cubicweb.toolsutils import Command, rm, create_dir, underline_title -from cubicweb.__pkginfo__ import version +from cubicweb.__pkginfo__ import version as cw_version # don't check duplicated commands, it occurs when reloading site_cubicweb CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.', - version=version, check_duplicated_command=False) + version=cw_version, check_duplicated_command=False) def wait_process_end(pid, maxtry=10, waittime=1): @@ -108,12 +104,12 @@ arguments = '[...]' options = ( ("force", - {'short': 'f', 'action' : 'store_true', + {'short': 'f', 'action': 'store_true', 'default': False, 'help': 'force command without asking confirmation', } ), - ) + ) actionverb = None def run(self, args): @@ -135,14 +131,14 @@ status = 0 for appid in args: if askconfirm: - print('*'*72) + print('*' * 72) if not ASK.confirm('%s instance %r ?' % (self.name, appid)): continue try: status = max(status, self.run_arg(appid)) except (KeyboardInterrupt, SystemExit): sys.stderr.write('%s aborted\n' % self.name) - return 2 # specific error code + return 2 # specific error code sys.exit(status) def run_arg(self, appid): @@ -151,16 +147,17 @@ status = cmdmeth(appid) or 0 except (ExecutionError, ConfigurationError) as ex: sys.stderr.write('instance %s not %s: %s\n' % ( - appid, self.actionverb, ex)) + appid, self.actionverb, ex)) status = 4 except Exception as ex: import traceback traceback.print_exc() sys.stderr.write('instance %s not %s: %s\n' % ( - appid, self.actionverb, ex)) + appid, self.actionverb, ex)) status = 8 return status + class InstanceCommandFork(InstanceCommand): """Same as `InstanceCommand`, but command is forked in a new environment for each argument @@ -168,12 +165,12 @@ def run_args(self, args, askconfirm): if len(args) > 1: - forkcmd = ' '.join(w for w in sys.argv if not w in args) + forkcmd = ' '.join(w for w in sys.argv if w not in args) else: forkcmd = None for appid in args: if askconfirm: - print('*'*72) + print('*' * 72) if not ASK.confirm('%s instance %r ?' % (self.name, appid)): continue if forkcmd: @@ -197,9 +194,9 @@ arguments = '[all|cubes|configurations|instances]' options = ( ('verbose', - {'short': 'v', 'action' : 'store_true', + {'short': 'v', 'action': 'store_true', 'help': "display more information."}), - ) + ) def run(self, args): """run the command with its specific arguments""" @@ -231,15 +228,14 @@ if mode in ('all', 'cubes'): cfgpb = ConfigurationProblem(cwcfg) try: - cubesdir = pathsep.join(cwcfg.cubes_search_path()) cube_names = available_cube_names(cwcfg) namesize = max(len(x) for x in cube_names) except ConfigurationError as ex: print('No cubes available:', ex) except ValueError: - print('No cubes available in %s' % cubesdir) + print('No cubes available') else: - print('Available cubes (%s):' % cubesdir) + print('Available cubes:') for cube in cube_names: try: tinfo = cwcfg.cube_pkginfo(cube) @@ -255,7 +251,7 @@ if not descr: descr = tinfo.__doc__ if descr: - print(' '+ ' \n'.join(descr.splitlines())) + print(' ' + ' \n'.join(descr.splitlines())) modes = detect_available_modes(cwcfg.cube_dir(cube)) print(' available modes: %s' % ', '.join(modes)) print() @@ -289,7 +285,7 @@ # configuration management problem solving cfgpb.solve() if cfgpb.warnings: - print('Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings)) + print('Warnings:\n', '\n'.join('* ' + txt for txt in cfgpb.warnings)) if cfgpb.errors: print('Errors:') for op, cube, version, src in cfgpb.errors: @@ -299,14 +295,18 @@ print(' version', version, end=' ') print('is not installed, but required by %s' % src) else: - print('* cube %s version %s is installed, but version %s is required by %s' % ( - cube, cfgpb.cubes[cube], version, src)) + print( + '* cube %s version %s is installed, but version %s is required by %s' + % (cube, cfgpb.cubes[cube], version, src) + ) + def check_options_consistency(config): if config.automatic and config.config_level > 0: raise BadCommandUsage('--automatic and --config-level should not be ' 'used together') + class CreateInstanceCommand(Command): """Create an instance from a cube. This is a unified command which can handle web / server / all-in-one installation @@ -325,20 +325,20 @@ min_args = max_args = 2 options = ( ('automatic', - {'short': 'a', 'action' : 'store_true', + {'short': 'a', 'action': 'store_true', 'default': False, 'help': 'automatic mode: never ask and use default answer to every ' 'question. this may require that your login match a database super ' 'user (allowed to create database & all).', }), ('config-level', - {'short': 'l', 'type' : 'int', 'metavar': '', + {'short': 'l', 'type': 'int', 'metavar': '', 'default': 0, 'help': 'configuration level (0..2): 0 will ask for essential ' 'configuration parameters only while 2 will ask for all parameters', }), ('config', - {'short': 'c', 'type' : 'choice', 'metavar': '', + {'short': 'c', 'type': 'choice', 'metavar': '', 'choices': ('all-in-one', 'repository', 'pyramid'), 'default': 'all-in-one', 'help': 'installation type, telling which part of an instance ' @@ -352,7 +352,7 @@ 'default': False, 'help': 'stop after creation and do not continue with db-create', }), - ) + ) def run(self, args): """run the command with its specific arguments""" @@ -376,12 +376,12 @@ print(', '.join(available_cube_names(cwcfg))) return # create the registry directory for this instance - print('\n'+underline_title('Creating the instance %s' % appid)) + print('\n' + underline_title('Creating the instance %s' % appid)) create_dir(config.apphome) # cubicweb-ctl configuration if not self.config.automatic: - print('\n'+underline_title('Configuring the instance (%s.conf)' - % configname)) + print('\n' + underline_title('Configuring the instance (%s.conf)' + % configname)) config.input_config('main', self.config.config_level) # configuration'specific stuff print() @@ -397,7 +397,6 @@ config.input_config(section, self.config.config_level) # write down configuration config.save() - self._handle_win32(config, appid) print('-> generated config %s' % config.main_config_file()) # handle i18n files structure # in the first cube given @@ -407,12 +406,12 @@ if errors: print('\n'.join(errors)) if self.config.automatic \ - or not ASK.confirm('error while compiling message catalogs, ' - 'continue anyway ?'): + or not ASK.confirm('error while compiling message catalogs, ' + 'continue anyway ?'): print('creation not completed') return # create the additional data directory for this instance - if config.appdatahome != config.apphome: # true in dev mode + if config.appdatahome != config.apphome: # true in dev mode create_dir(config.appdatahome) create_dir(join(config.appdatahome, 'backup')) if config['uid']: @@ -424,29 +423,6 @@ if not self.config.no_db_create: helper.postcreate(self.config.automatic, self.config.config_level) - def _handle_win32(self, config, appid): - if sys.platform != 'win32': - return - service_template = """ -import sys -import win32serviceutil -sys.path.insert(0, r"%(CWPATH)s") - -from cubicweb.etwist.service import CWService - -classdict = {'_svc_name_': 'cubicweb-%(APPID)s', - '_svc_display_name_': 'CubicWeb ' + '%(CNAME)s', - 'instance': '%(APPID)s'} -%(CNAME)sService = type('%(CNAME)sService', (CWService,), classdict) - -if __name__ == '__main__': - win32serviceutil.HandleCommandLine(%(CNAME)sService) -""" - open(join(config.apphome, 'win32svc.py'), 'wb').write( - service_template % {'APPID': appid, - 'CNAME': appid.capitalize(), - 'CWPATH': abspath(join(dirname(__file__), '..'))}) - class DeleteInstanceCommand(Command): """Delete an instance. Will remove instance's files and @@ -483,184 +459,12 @@ # instance commands ######################################################## -class StartInstanceCommand(InstanceCommandFork): - """Start the given instances. If no instance is given, start them all. - - ... - identifiers of the instances to start. If no instance is - given, start them all. - """ - name = 'start' - actionverb = 'started' - options = ( - ("debug", - {'short': 'D', 'action' : 'store_true', - 'help': 'start server in debug mode.'}), - ("force", - {'short': 'f', 'action' : 'store_true', - 'default': False, - 'help': 'start the instance even if it seems to be already \ -running.'}), - ('profile', - {'short': 'P', 'type' : 'string', 'metavar': '', - 'default': None, - 'help': 'profile code and use the specified file to store stats', - }), - ('loglevel', - {'short': 'l', 'type' : 'choice', 'metavar': '', - 'default': None, 'choices': ('debug', 'info', 'warning', 'error'), - 'help': 'debug if -D is set, error otherwise', - }), - ('param', - {'short': 'p', 'type' : 'named', 'metavar' : 'key1:value1,key2:value2', - 'default': {}, - 'help': 'override configuration file option with .', - }), - ) - - def start_instance(self, appid): - """start the instance's server""" - try: - import twisted # noqa - except ImportError: - msg = ( - "Twisted is required by the 'start' command\n" - "Either install it, or use one of the alternative commands:\n" - "- '{ctl} pyramid {appid}'\n" - "- '{ctl} wsgi {appid}'\n") - raise ExecutionError(msg.format(ctl='cubicweb-ctl', appid=appid)) - config = cwcfg.config_for(appid, debugmode=self['debug']) - # override config file values with cmdline options - config.cmdline_options = self.config.param - init_cmdline_log_threshold(config, self['loglevel']) - if self['profile']: - config.global_set_option('profile', self.config.profile) - helper = self.config_helper(config, cmdname='start') - pidf = config['pid-file'] - if exists(pidf) and not self['force']: - msg = "%s seems to be running. Remove %s by hand if necessary or use \ -the --force option." - raise ExecutionError(msg % (appid, pidf)) - if helper.start_server(config) == 1: - print('instance %s started' % appid) - - def init_cmdline_log_threshold(config, loglevel): if loglevel is not None: config.global_set_option('log-threshold', loglevel.upper()) config.init_log(config['log-threshold'], force=True) -class StopInstanceCommand(InstanceCommand): - """Stop the given instances. - - ... - identifiers of the instances to stop. If no instance is - given, stop them all. - """ - name = 'stop' - actionverb = 'stopped' - - def stop_instance(self, appid): - """stop the instance's server""" - config = cwcfg.config_for(appid) - helper = self.config_helper(config, cmdname='stop') - helper.poststop() # do this anyway - pidf = config['pid-file'] - if not exists(pidf): - sys.stderr.write("%s doesn't exist.\n" % pidf) - return - import signal - pid = int(open(pidf).read().strip()) - try: - kill(pid, signal.SIGTERM) - except Exception: - sys.stderr.write("process %s seems already dead.\n" % pid) - else: - try: - wait_process_end(pid) - except ExecutionError as ex: - sys.stderr.write('%s\ntrying SIGKILL\n' % ex) - try: - kill(pid, signal.SIGKILL) - except Exception: - # probably dead now - pass - wait_process_end(pid) - try: - remove(pidf) - except OSError: - # already removed by twistd - pass - print('instance %s stopped' % appid) - - -class RestartInstanceCommand(StartInstanceCommand): - """Restart the given instances. - - ... - identifiers of the instances to restart. If no instance is - given, restart them all. - """ - name = 'restart' - actionverb = 'restarted' - - def restart_instance(self, appid): - StopInstanceCommand(self.logger).stop_instance(appid) - self.start_instance(appid) - - -class ReloadConfigurationCommand(RestartInstanceCommand): - """Reload the given instances. This command is equivalent to a - restart for now. - - ... - identifiers of the instances to reload. If no instance is - given, reload them all. - """ - name = 'reload' - - def reload_instance(self, appid): - self.restart_instance(appid) - - -class StatusCommand(InstanceCommand): - """Display status information about the given instances. - - ... - identifiers of the instances to status. If no instance is - given, get status information about all registered instances. - """ - name = 'status' - options = () - - @staticmethod - def status_instance(appid): - """print running status information for an instance""" - status = 0 - for mode in cwcfg.possible_configurations(appid): - config = cwcfg.config_for(appid, mode) - print('[%s-%s]' % (appid, mode), end=' ') - try: - pidf = config['pid-file'] - except KeyError: - print('buggy instance, pid file not specified') - continue - if not exists(pidf): - print("doesn't seem to be running") - status = 1 - continue - pid = int(open(pidf).read().strip()) - # trick to guess whether or not the process is running - try: - getpgid(pid) - except OSError: - print("should be running with pid %s but the process can not be found" % pid) - status = 1 - continue - print("running with pid %s" % (pid)) - return status - class UpgradeInstanceCommand(InstanceCommandFork): """Upgrade an instance after cubicweb and/or component(s) upgrade. @@ -676,17 +480,17 @@ actionverb = 'upgraded' options = InstanceCommand.options + ( ('force-cube-version', - {'short': 't', 'type' : 'named', 'metavar': 'cube1:X.Y.Z,cube2:X.Y.Z', + {'short': 't', 'type': 'named', 'metavar': 'cube1:X.Y.Z,cube2:X.Y.Z', 'default': None, 'help': 'force migration from the indicated version for the specified cube(s).'}), ('force-cubicweb-version', - {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', + {'short': 'e', 'type': 'string', 'metavar': 'X.Y.Z', 'default': None, 'help': 'force migration from the indicated cubicweb version.'}), ('fs-only', - {'short': 's', 'action' : 'store_true', + {'short': 's', 'action': 'store_true', 'default': False, 'help': 'only upgrade files on the file system, not the database.'}), @@ -695,40 +499,34 @@ 'default': False, 'help': 'do NOT update config file if set.'}), - ('nostartstop', - {'short': 'n', 'action' : 'store_true', - 'default': False, - 'help': 'don\'t try to stop instance before migration and to restart it after.'}), - ('verbosity', - {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', + {'short': 'v', 'type': 'int', 'metavar': '<0..2>', 'default': 1, 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ for everything."}), ('backup-db', - {'short': 'b', 'type' : 'yn', 'metavar': '', + {'short': 'b', 'type': 'yn', 'metavar': '', 'default': None, - 'help': "Backup the instance database before upgrade.\n"\ + 'help': "Backup the instance database before upgrade.\n" "If the option is ommitted, confirmation will be ask.", }), ('ext-sources', - {'short': 'E', 'type' : 'csv', 'metavar': '', + {'short': 'E', 'type': 'csv', 'metavar': '', 'default': None, 'help': "For multisources instances, specify to which sources the \ repository should connect to for upgrading. When unspecified or 'migration' is \ given, appropriate sources for migration will be automatically selected \ (recommended). If 'all' is given, will connect to all defined sources.", }), - ) + ) def upgrade_instance(self, appid): print('\n' + underline_title('Upgrading the instance %s' % appid)) from logilab.common.changelog import Version config = cwcfg.config_for(appid) - instance_running = exists(config['pid-file']) - config.repairing = True # notice we're not starting the server + config.repairing = True # notice we're not starting the server config.verbosity = self.config.verbosity set_sources_mode = getattr(config, 'set_sources_mode', None) if set_sources_mode is not None: @@ -750,7 +548,7 @@ config.error('no version information for %s' % cube) continue if installedversion > applversion: - toupgrade.append( (cube, applversion, installedversion) ) + toupgrade.append((cube, applversion, installedversion)) cubicwebversion = config.cubicweb_version() if self.config.force_cubicweb_version: applcubicwebversion = Version(self.config.force_cubicweb_version) @@ -759,9 +557,6 @@ applcubicwebversion = vcconf.get('cubicweb') if cubicwebversion > applcubicwebversion: toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion)) - # only stop once we're sure we have something to do - if instance_running and not self.config.nostartstop: - StopInstanceCommand(self.logger).stop_instance(appid) # run cubicweb/componants migration scripts if self.config.fs_only or toupgrade: for cube, fromversion, toversion in toupgrade: @@ -783,13 +578,6 @@ if helper: helper.postupgrade(repo) print('-> instance migrated.') - if instance_running and not self.config.nostartstop: - # restart instance through fork to get a proper environment, avoid - # uicfg pb (and probably gettext catalogs, to check...) - forkcmd = '%s start %s' % (sys.argv[0], appid) - status = system(forkcmd) - if status: - print('%s exited with status %s' % (forkcmd, status)) print() def i18nupgrade(self, config): @@ -828,7 +616,8 @@ config.set_sources_mode(('migration',)) vcconf = config.repository().get_versions() for key in sorted(vcconf): - print(key+': %s.%s.%s' % vcconf[key]) + print(key + ': %s.%s.%s' % vcconf[key]) + class ShellCommand(Command): """Run an interactive migration shell on an instance. This is a python shell @@ -851,15 +640,15 @@ min_args = 1 options = ( ('system-only', - {'short': 'S', 'action' : 'store_true', + {'short': 'S', 'action': 'store_true', 'help': 'only connect to the system source when the instance is ' 'using multiple sources. You can\'t use this option and the ' '--ext-sources option at the same time.', 'group': 'local' - }), + }), ('ext-sources', - {'short': 'E', 'type' : 'csv', 'metavar': '', + {'short': 'E', 'type': 'csv', 'metavar': '', 'help': "For multisources instances, specify to which sources the \ repository should connect to for upgrading. When unspecified or 'all' given, \ will connect to all defined sources. If 'migration' is given, appropriate \ @@ -868,19 +657,12 @@ }), ('force', - {'short': 'f', 'action' : 'store_true', + {'short': 'f', 'action': 'store_true', 'help': 'don\'t check instance is up to date.', 'group': 'local' }), - ('repo-uri', - {'short': 'H', 'type' : 'string', 'metavar': '://<[host][:port]>', - 'help': 'URI of the CubicWeb repository to connect to. URI can be \ -a ZMQ URL or inmemory:// (default) use an in-memory repository. THIS OPTION IS DEPRECATED, \ -directly give URI as instance id instead', - 'group': 'remote' - }), - ) + ) def _get_mih(self, appid): """ returns migration context handler & shutdown function """ @@ -899,12 +681,6 @@ def run(self, args): appuri = args.pop(0) - if self.config.repo_uri: - warn('[3.16] --repo-uri option is deprecated, directly give the URI as instance id', - DeprecationWarning) - if urlparse(self.config.repo_uri).scheme == 'inmemory': - appuri = '%s/%s' % (self.config.repo_uri.rstrip('/'), appuri) - mih, shutdown_callback = self._get_mih(appuri) try: with mih.cnx: @@ -914,8 +690,8 @@ # remember that usage requires instance appid as first argument scripts, args = self.cmdline_parser.largs[1:], self.cmdline_parser.rargs for script in scripts: - mih.cmd_process_script(script, scriptargs=args) - mih.commit() + mih.cmd_process_script(script, scriptargs=args) + mih.commit() else: mih.interactive_shell() finally: @@ -935,7 +711,7 @@ def i18ninstance_instance(appid): """recompile instance's messages catalogs""" config = cwcfg.config_for(appid) - config.quick_start = True # notify this is not a regular start + config.quick_start = True # notify this is not a regular start repo = config.repository() if config._cubes is None: # web only config @@ -967,6 +743,7 @@ for cube in cwcfg.available_cubes(): print(cube) + class ConfigureInstanceCommand(InstanceCommand): """Configure instance. @@ -976,13 +753,14 @@ name = 'configure' actionverb = 'configured' - options = merge_options(InstanceCommand.options + - (('param', - {'short': 'p', 'type' : 'named', 'metavar' : 'key1:value1,key2:value2', - 'default': None, - 'help': 'set to in configuration file.', - }), - )) + options = merge_options( + InstanceCommand.options + ( + ('param', + {'short': 'p', 'type': 'named', 'metavar': 'key1:value1,key2:value2', + 'default': None, + 'help': 'set to in configuration file.'}), + ), + ) def configure_instance(self, appid): if self.config.param is not None: @@ -991,82 +769,13 @@ try: appcfg.global_set_option(key, value) except KeyError: - raise ConfigurationError('unknown configuration key "%s" for mode %s' % (key, appcfg.name)) + raise ConfigurationError( + 'unknown configuration key "%s" for mode %s' % (key, appcfg.name)) appcfg.save() -# WSGI ######### - -WSGI_CHOICES = {} -try: - from cubicweb.wsgi import server as stdlib_server -except ImportError: - pass -else: - WSGI_CHOICES['stdlib'] = stdlib_server -try: - from cubicweb.wsgi import wz -except ImportError: - pass -else: - WSGI_CHOICES['werkzeug'] = wz -try: - from cubicweb.wsgi import tnd -except ImportError: - pass -else: - WSGI_CHOICES['tornado'] = tnd - - -def wsgichoices(): - return tuple(WSGI_CHOICES) - -if WSGI_CHOICES: - class WSGIStartHandler(InstanceCommand): - """Start an interactive wsgi server """ - name = 'wsgi' - actionverb = 'started' - arguments = '' - - @property - def options(self): - return ( - ("debug", - {'short': 'D', 'action': 'store_true', - 'default': False, - 'help': 'start server in debug mode.'}), - ('method', - {'short': 'm', - 'type': 'choice', - 'metavar': '', - 'default': 'stdlib', - 'choices': wsgichoices(), - 'help': 'wsgi utility/method'}), - ('loglevel', - {'short': 'l', - 'type': 'choice', - 'metavar': '', - 'default': None, - 'choices': ('debug', 'info', 'warning', 'error'), - 'help': 'debug if -D is set, error otherwise', - }), - ) - - def wsgi_instance(self, appid): - config = cwcfg.config_for(appid, debugmode=self['debug']) - init_cmdline_log_threshold(config, self['loglevel']) - assert config.name == 'all-in-one' - meth = self['method'] - server = WSGI_CHOICES[meth] - return server.run(config) - - CWCTL.register(WSGIStartHandler) - - for cmdcls in (ListCommand, CreateInstanceCommand, DeleteInstanceCommand, - StartInstanceCommand, StopInstanceCommand, RestartInstanceCommand, - ReloadConfigurationCommand, StatusCommand, UpgradeInstanceCommand, ListVersionsInstanceCommand, ShellCommand, @@ -1077,10 +786,8 @@ CWCTL.register(cmdcls) - def run(args=sys.argv[1:]): """command line tool""" - import os filterwarnings('default', category=DeprecationWarning) cwcfg.load_cwctl_plugins() try: @@ -1092,5 +799,6 @@ print(err) sys.exit(2) + if __name__ == '__main__': run() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/cwgettext.py --- a/cubicweb/cwgettext.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/cwgettext.py Fri May 24 16:29:14 2019 +0200 @@ -18,8 +18,6 @@ import gettext -from six import PY3 - class cwGNUTranslations(gettext.GNUTranslations): # The encoding of a msgctxt and a msgid in a .mo file is @@ -85,8 +83,7 @@ else: return msgid2 - if PY3: - ugettext = gettext.GNUTranslations.gettext + ugettext = gettext.GNUTranslations.gettext def upgettext(self, context, message): ctxt_message_id = self.CONTEXT_ENCODING % (context, message) @@ -97,7 +94,7 @@ return self.ugettext(message) if self._fallback: return self._fallback.upgettext(context, message) - return unicode(message) + return str(message) return tmsg def unpgettext(self, context, msgid1, msgid2, n): @@ -108,9 +105,9 @@ if self._fallback: return self._fallback.unpgettext(context, msgid1, msgid2, n) if n == 1: - tmsg = unicode(msgid1) + tmsg = str(msgid1) else: - tmsg = unicode(msgid2) + tmsg = str(msgid2) return tmsg diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/cwvreg.py --- a/cubicweb/cwvreg.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/cwvreg.py Fri May 24 16:29:14 2019 +0200 @@ -21,12 +21,9 @@ import sys from os.path import join, dirname, realpath -from warnings import warn from datetime import datetime, date, time, timedelta from functools import reduce -from six import text_type, binary_type - from logilab.common.decorators import cached, clear_cache from logilab.common.deprecation import class_deprecated from logilab.common.modutils import clean_sys_modules @@ -128,7 +125,7 @@ def register(self, obj, **kwargs): obj = related_appobject(obj) oid = kwargs.get('oid') or obj.__regid__ - if oid != 'Any' and not oid in self.schema: + if oid != 'Any' and oid not in self.schema: self.error('don\'t register %s, %s type not defined in the ' 'schema', obj, oid) return @@ -219,9 +216,9 @@ """ obj = self.select(oid, req, rset=rset, **kwargs) res = obj.render(**kwargs) - if isinstance(res, text_type): + if isinstance(res, str): return res.encode(req.encoding) - assert isinstance(res, binary_type) + assert isinstance(res, bytes) return res def possible_views(self, req, rset=None, **kwargs): @@ -253,7 +250,7 @@ if rset is None: actions = self.poss_visible_objects(req, rset=rset, **kwargs) else: - actions = rset.possible_actions(**kwargs) # cached implementation + actions = rset.possible_actions(**kwargs) # cached implementation result = {} for action in actions: result.setdefault(action.category, []).append(action) @@ -295,21 +292,6 @@ return thisctxcomps -class BwCompatCWRegistry(object): - def __init__(self, vreg, oldreg, redirecttoreg): - self.vreg = vreg - self.oldreg = oldreg - self.redirecto = redirecttoreg - - def __getattr__(self, attr): - warn('[3.10] you should now use the %s registry instead of the %s registry' - % (self.redirecto, self.oldreg), DeprecationWarning, stacklevel=2) - return getattr(self.vreg[self.redirecto], attr) - - def clear(self): pass - def initialization_completed(self): pass - - class CWRegistryStore(RegistryStore): """Central registry for the cubicweb instance, extending the generic RegistryStore with some cubicweb specific stuff. @@ -366,8 +348,6 @@ sys.path.remove(CW_SOFTWARE_ROOT) self.schema = None self.initialized = False - self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents') - self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents') def setdefault(self, regid): try: @@ -379,12 +359,14 @@ def items(self): return [item for item in super(CWRegistryStore, self).items() if not item[0] in ('propertydefs', 'propertyvalues')] + def iteritems(self): return (item for item in super(CWRegistryStore, self).items() if not item[0] in ('propertydefs', 'propertyvalues')) def values(self): return [value for key, value in self.items()] + def itervalues(self): return (value for key, value in self.items()) @@ -479,7 +461,7 @@ # bad class reference pb after reloading cfg = self.config for cube in cfg.expand_cubes(cubes, with_recommends=True): - if not cube in cubes: + if cube not in cubes: cube_modnames = cfg.appobjects_cube_modnames(cube) self._cleanup_sys_modules(cube_modnames) self.register_modnames(modnames) @@ -523,12 +505,6 @@ if depends_on is not None: self._needs_appobject[obj] = depends_on - def register_objects(self, path): - """overriden to give cubicweb's extrapath (eg cubes package's __path__) - """ - super(CWRegistryStore, self).register_objects( - path, self.config.extrapath) - def initialization_completed(self): """cw specific code once vreg initialization is completed: @@ -555,7 +531,7 @@ registry.objid(obj), ' or '.join(regids), regname) self.unregister(obj) super(CWRegistryStore, self).initialization_completed() - if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode + if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode for rtags in self['uicfg'].values(): for rtag in rtags: # don't check rtags if we don't want to cleanup_unused_appobjects @@ -634,8 +610,8 @@ vocab = pdef['vocabulary'] if vocab is not None: if callable(vocab): - vocab = vocab(None) # XXX need a req object - if not value in vocab: + vocab = vocab(None) # XXX need a req object + if value not in vocab: raise ValueError(_('unauthorized value')) return value @@ -658,11 +634,11 @@ # XXX unify with yams.constraints.BASE_CONVERTERS? YAMS_TO_PY = BASE_CONVERTERS.copy() YAMS_TO_PY.update({ - 'Bytes': Binary, - 'Date': date, - 'Datetime': datetime, + 'Bytes': Binary, + 'Date': date, + 'Datetime': datetime, 'TZDatetime': datetime, - 'Time': time, - 'TZTime': time, - 'Interval': timedelta, - }) + 'Time': time, + 'TZTime': time, + 'Interval': timedelta, +}) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/__init__.py --- a/cubicweb/dataimport/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -32,4 +32,3 @@ from cubicweb.dataimport.stores import * from cubicweb.dataimport.pgstore import * from cubicweb.dataimport.csv import * -from cubicweb.dataimport.deprecated import * diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/csv.py --- a/cubicweb/dataimport/csv.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/csv.py Fri May 24 16:29:14 2019 +0200 @@ -16,19 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """Functions to help importing CSV data""" -from __future__ import absolute_import, print_function - import codecs import csv as csvmod -import warnings - -from six import PY2, PY3, string_types from logilab.common import shellutils def count_lines(stream_or_filename): - if isinstance(stream_or_filename, string_types): + if isinstance(stream_or_filename, str): f = open(stream_or_filename) else: f = stream_or_filename @@ -41,16 +36,9 @@ def ucsvreader_pb(stream_or_path, encoding='utf-8', delimiter=',', quotechar='"', - skipfirst=False, withpb=True, skip_empty=True, separator=None, - quote=None): + skipfirst=False, withpb=True, skip_empty=True): """same as :func:`ucsvreader` but a progress bar is displayed as we iter on rows""" - if separator is not None: - delimiter = separator - warnings.warn("[3.20] 'separator' kwarg is deprecated, use 'delimiter' instead") - if quote is not None: - quotechar = quote - warnings.warn("[3.20] 'quote' kwarg is deprecated, use 'quotechar' instead") - if isinstance(stream_or_path, string_types): + if isinstance(stream_or_path, str): stream = open(stream_or_path, 'rb') else: stream = stream_or_path @@ -68,8 +56,7 @@ def ucsvreader(stream, encoding='utf-8', delimiter=',', quotechar='"', - skipfirst=False, ignore_errors=False, skip_empty=True, - separator=None, quote=None): + skipfirst=False, ignore_errors=False, skip_empty=True): """A csv reader that accepts files with any encoding and outputs unicode strings @@ -77,25 +64,14 @@ separators) will be skipped. This is useful for Excel exports which may be full of such lines. """ - if PY3: - stream = codecs.getreader(encoding)(stream) - if separator is not None: - delimiter = separator - warnings.warn("[3.20] 'separator' kwarg is deprecated, use 'delimiter' instead") - if quote is not None: - quotechar = quote - warnings.warn("[3.20] 'quote' kwarg is deprecated, use 'quotechar' instead") + stream = codecs.getreader(encoding)(stream) it = iter(csvmod.reader(stream, delimiter=delimiter, quotechar=quotechar)) if not ignore_errors: if skipfirst: next(it) for row in it: - if PY2: - decoded = [item.decode(encoding) for item in row] - else: - decoded = row - if not skip_empty or any(decoded): - yield decoded + if not skip_empty or any(row): + yield row else: if skipfirst: try: @@ -112,9 +88,5 @@ # Error in CSV, ignore line and continue except csvmod.Error: continue - if PY2: - decoded = [item.decode(encoding) for item in row] - else: - decoded = row - if not skip_empty or any(decoded): - yield decoded + if not skip_empty or any(row): + yield row diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/deprecated.py --- a/cubicweb/dataimport/deprecated.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,463 +0,0 @@ -# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Old and deprecated dataimport API that provides tools to import tabular data. - - -Example of use (run this with `cubicweb-ctl shell instance import-script.py`): - -.. sourcecode:: python - - from cubicweb.dataimport import * - # define data generators - GENERATORS = [] - - USERS = [('Prenom', 'firstname', ()), - ('Nom', 'surname', ()), - ('Identifiant', 'login', ()), - ] - - def gen_users(ctl): - for row in ctl.iter_and_commit('utilisateurs'): - entity = mk_entity(row, USERS) - entity['upassword'] = 'motdepasse' - ctl.check('login', entity['login'], None) - entity = ctl.store.prepare_insert_entity('CWUser', **entity) - email = ctl.store.prepare_insert_entity('EmailAddress', address=row['email']) - ctl.store.prepare_insert_relation(entity, 'use_email', email) - ctl.store.rql('SET U in_group G WHERE G name "users", U eid %(x)s', {'x': entity}) - - CHK = [('login', check_doubles, 'Utilisateurs Login', - 'Deux utilisateurs ne devraient pas avoir le meme login.'), - ] - - GENERATORS.append( (gen_users, CHK) ) - - # create controller - ctl = CWImportController(RQLObjectStore(cnx)) - ctl.askerror = 1 - ctl.generators = GENERATORS - ctl.data['utilisateurs'] = lazytable(ucsvreader(open('users.csv'))) - # run - ctl.run() - -.. BUG file with one column are not parsable -.. TODO rollback() invocation is not possible yet -""" -from __future__ import print_function - -import sys -import traceback -from io import StringIO - -from six import add_metaclass - -from logilab.common import attrdict, shellutils -from logilab.common.date import strptime -from logilab.common.deprecation import deprecated, class_deprecated - -from cubicweb import QueryError -from cubicweb.dataimport import callfunc_every - - -@deprecated('[3.21] deprecated') -def lazytable(reader): - """The first row is taken to be the header of the table and - used to output a dict for each row of data. - - >>> data = lazytable(ucsvreader(open(filename))) - """ - header = next(reader) - for row in reader: - yield dict(zip(header, row)) - - -@deprecated('[3.21] deprecated') -def lazydbtable(cu, table, headers, orderby=None): - """return an iterator on rows of a sql table. On each row, fetch columns - defined in headers and return values as a dictionary. - - >>> data = lazydbtable(cu, 'experimentation', ('id', 'nickname', 'gps')) - """ - sql = 'SELECT %s FROM %s' % (','.join(headers), table,) - if orderby: - sql += ' ORDER BY %s' % ','.join(orderby) - cu.execute(sql) - while True: - row = cu.fetchone() - if row is None: - break - yield dict(zip(headers, row)) - - -@deprecated('[3.21] deprecated') -def tell(msg): - print(msg) - - -@deprecated('[3.21] deprecated') -def confirm(question): - """A confirm function that asks for yes/no/abort and exits on abort.""" - answer = shellutils.ASK.ask(question, ('Y', 'n', 'abort'), 'Y') - if answer == 'abort': - sys.exit(1) - return answer == 'Y' - - -@add_metaclass(class_deprecated) -class catch_error(object): - """Helper for @contextmanager decorator.""" - __deprecation_warning__ = '[3.21] deprecated' - - def __init__(self, ctl, key='unexpected error', msg=None): - self.ctl = ctl - self.key = key - self.msg = msg - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - if type is not None: - if issubclass(type, (KeyboardInterrupt, SystemExit)): - return # re-raise - if self.ctl.catcherrors: - self.ctl.record_error(self.key, None, type, value, traceback) - return True # silent - -@deprecated('[3.21] deprecated') -def mk_entity(row, map): - """Return a dict made from sanitized mapped values. - - ValueError can be raised on unexpected values found in checkers - - >>> row = {'myname': u'dupont'} - >>> map = [('myname', u'name', (call_transform_method('title'),))] - >>> mk_entity(row, map) - {'name': u'Dupont'} - >>> row = {'myname': u'dupont', 'optname': u''} - >>> map = [('myname', u'name', (call_transform_method('title'),)), - ... ('optname', u'MARKER', (optional,))] - >>> mk_entity(row, map) - {'name': u'Dupont', 'optname': None} - """ - res = {} - assert isinstance(row, dict) - assert isinstance(map, list) - for src, dest, funcs in map: - try: - res[dest] = row[src] - except KeyError: - continue - try: - for func in funcs: - res[dest] = func(res[dest]) - if res[dest] is None: - break - except ValueError as err: - exc = ValueError('error with %r field: %s' % (src, err)) - exc.__traceback__ = sys.exc_info()[-1] - raise exc - return res - - -# base sanitizing/coercing functions ########################################### - -@deprecated('[3.21] deprecated') -def optional(value): - """checker to filter optional field - - If value is undefined (ex: empty string), return None that will - break the checkers validation chain - - General use is to add 'optional' check in first condition to avoid - ValueError by further checkers - - >>> MAPPER = [(u'value', 'value', (optional, int))] - >>> row = {'value': u'XXX'} - >>> mk_entity(row, MAPPER) - {'value': None} - >>> row = {'value': u'100'} - >>> mk_entity(row, MAPPER) - {'value': 100} - """ - if value: - return value - return None - - -@deprecated('[3.21] deprecated') -def required(value): - """raise ValueError if value is empty - - This check should be often found in last position in the chain. - """ - if value: - return value - raise ValueError("required") - - -@deprecated('[3.21] deprecated') -def todatetime(format='%d/%m/%Y'): - """return a transformation function to turn string input value into a - `datetime.datetime` instance, using given format. - - Follow it by `todate` or `totime` functions from `logilab.common.date` if - you want a `date`/`time` instance instead of `datetime`. - """ - def coerce(value): - return strptime(value, format) - return coerce - - -@deprecated('[3.21] deprecated') -def call_transform_method(methodname, *args, **kwargs): - """return value returned by calling the given method on input""" - def coerce(value): - return getattr(value, methodname)(*args, **kwargs) - return coerce - - -@deprecated('[3.21] deprecated') -def call_check_method(methodname, *args, **kwargs): - """check value returned by calling the given method on input is true, - else raise ValueError - """ - def check(value): - if getattr(value, methodname)(*args, **kwargs): - return value - raise ValueError('%s not verified on %r' % (methodname, value)) - return check - - -# base integrity checking functions ############################################ - -@deprecated('[3.21] deprecated') -def check_doubles(buckets): - """Extract the keys that have more than one item in their bucket.""" - return [(k, len(v)) for k, v in buckets.items() if len(v) > 1] - - -@deprecated('[3.21] deprecated') -def check_doubles_not_none(buckets): - """Extract the keys that have more than one item in their bucket.""" - return [(k, len(v)) for k, v in buckets.items() - if k is not None and len(v) > 1] - - -@add_metaclass(class_deprecated) -class ObjectStore(object): - """Store objects in memory for *faster* validation (development mode) - - But it will not enforce the constraints of the schema and hence will miss some problems - - >>> store = ObjectStore() - >>> user = store.prepare_insert_entity('CWUser', login=u'johndoe') - >>> group = store.prepare_insert_entity('CWUser', name=u'unknown') - >>> store.prepare_insert_relation(user, 'in_group', group) - """ - __deprecation_warning__ = '[3.21] use the new importer API' - - def __init__(self): - self.items = [] - self.eids = {} - self.types = {} - self.relations = set() - self.indexes = {} - - def prepare_insert_entity(self, etype, **data): - """Given an entity type, attributes and inlined relations, return an eid for the entity that - would be inserted with a real store. - """ - data = attrdict(data) - data['eid'] = eid = len(self.items) - self.items.append(data) - self.eids[eid] = data - self.types.setdefault(etype, []).append(eid) - return eid - - def prepare_update_entity(self, etype, eid, **kwargs): - """Given an entity type and eid, updates the corresponding fake entity with specified - attributes and inlined relations. - """ - assert eid in self.types[etype], 'Trying to update with wrong type %s' % etype - data = self.eids[eid] - data.update(kwargs) - - def prepare_insert_relation(self, eid_from, rtype, eid_to, **kwargs): - """Store into the `relations` attribute that a relation ``rtype`` exists between entities - with eids ``eid_from`` and ``eid_to``. - """ - relation = eid_from, rtype, eid_to - self.relations.add(relation) - return relation - - def flush(self): - """Nothing to flush for this store.""" - pass - - def commit(self): - """Nothing to commit for this store.""" - return - - def finish(self): - """Nothing to do once import is terminated for this store.""" - pass - - @property - def nb_inserted_entities(self): - return len(self.eids) - - @property - def nb_inserted_types(self): - return len(self.types) - - @property - def nb_inserted_relations(self): - return len(self.relations) - - @deprecated('[3.21] use prepare_insert_entity instead') - def create_entity(self, etype, **data): - self.prepare_insert_entity(etype, **data) - return attrdict(data) - - @deprecated('[3.21] use prepare_insert_relation instead') - def relate(self, eid_from, rtype, eid_to, **kwargs): - self.prepare_insert_relation(eid_from, rtype, eid_to, **kwargs) - - -@add_metaclass(class_deprecated) -class CWImportController(object): - """Controller of the data import process. - - >>> ctl = CWImportController(store) - >>> ctl.generators = list_of_data_generators - >>> ctl.data = dict_of_data_tables - >>> ctl.run() - """ - __deprecation_warning__ = '[3.21] use the new importer API' - - def __init__(self, store, askerror=0, catcherrors=None, tell=tell, - commitevery=50): - self.store = store - self.generators = None - self.data = {} - self.errors = None - self.askerror = askerror - if catcherrors is None: - catcherrors = askerror - self.catcherrors = catcherrors - self.commitevery = commitevery # set to None to do a single commit - self._tell = tell - - def check(self, type, key, value): - self._checks.setdefault(type, {}).setdefault(key, []).append(value) - - def check_map(self, entity, key, map, default): - try: - entity[key] = map[entity[key]] - except KeyError: - self.check(key, entity[key], None) - entity[key] = default - - def record_error(self, key, msg=None, type=None, value=None, tb=None): - tmp = StringIO() - if type is None: - traceback.print_exc(file=tmp) - else: - traceback.print_exception(type, value, tb, file=tmp) - # use a list to avoid counting a errors instead of one - errorlog = self.errors.setdefault(key, []) - if msg is None: - errorlog.append(tmp.getvalue().splitlines()) - else: - errorlog.append( (msg, tmp.getvalue().splitlines()) ) - - def run(self): - self.errors = {} - if self.commitevery is None: - self.tell('Will commit all or nothing.') - else: - self.tell('Will commit every %s iterations' % self.commitevery) - for func, checks in self.generators: - self._checks = {} - func_name = func.__name__ - self.tell("Run import function '%s'..." % func_name) - try: - func(self) - except Exception: - if self.catcherrors: - self.record_error(func_name, 'While calling %s' % func.__name__) - else: - self._print_stats() - raise - for key, func, title, help in checks: - buckets = self._checks.get(key) - if buckets: - err = func(buckets) - if err: - self.errors[title] = (help, err) - try: - txuuid = self.store.commit() - if txuuid is not None: - self.tell('Transaction commited (txuuid: %s)' % txuuid) - except QueryError as ex: - self.tell('Transaction aborted: %s' % ex) - self._print_stats() - if self.errors: - if self.askerror == 2 or (self.askerror and confirm('Display errors ?')): - from pprint import pformat - for errkey, error in self.errors.items(): - self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1]))) - self.tell(pformat(sorted(error[1]))) - - def _print_stats(self): - nberrors = sum(len(err) for err in self.errors.values()) - self.tell('\nImport statistics: %i entities, %i types, %i relations and %i errors' - % (self.store.nb_inserted_entities, - self.store.nb_inserted_types, - self.store.nb_inserted_relations, - nberrors)) - - def get_data(self, key): - return self.data.get(key) - - def index(self, name, key, value, unique=False): - """create a new index - - If unique is set to True, only first occurence will be kept not the following ones - """ - if unique: - try: - if value in self.store.indexes[name][key]: - return - except KeyError: - # we're sure that one is the first occurence; so continue... - pass - self.store.indexes.setdefault(name, {}).setdefault(key, []).append(value) - - def tell(self, msg): - self._tell(msg) - - def iter_and_commit(self, datakey): - """iter rows, triggering commit every self.commitevery iterations""" - if self.commitevery is None: - return self.get_data(datakey) - else: - return callfunc_every(self.store.commit, - self.commitevery, - self.get_data(datakey)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/importer.py --- a/cubicweb/dataimport/importer.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/importer.py Fri May 24 16:29:14 2019 +0200 @@ -30,8 +30,6 @@ from collections import defaultdict import logging -import six - from logilab.mtconverter import xml_escape from cubicweb import Binary @@ -74,7 +72,7 @@ for extentity in extentities: if extentity.extid not in extid2eid: cwuri = extentity.extid - if isinstance(cwuri, six.binary_type): + if isinstance(cwuri, bytes): cwuri = cwuri.decode('utf-8') extentity.values.setdefault('cwuri', set([cwuri])) yield extentity diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/massive_store.py --- a/cubicweb/dataimport/massive_store.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/massive_store.py Fri May 24 16:29:14 2019 +0200 @@ -22,9 +22,6 @@ import logging from uuid import uuid4 -from six import text_type -from six.moves import range - from cubicweb.dataimport import stores, pgstore from cubicweb.server.schema2sql import eschema_sql_def @@ -70,7 +67,7 @@ """ super(MassiveObjectStore, self).__init__(cnx) - self.uuid = text_type(uuid4()).replace('-', '') + self.uuid = str(uuid4()).replace('-', '') self.slave_mode = slave_mode if metagen is None: metagen = stores.MetadataGenerator(cnx) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/pgstore.py --- a/cubicweb/dataimport/pgstore.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/pgstore.py Fri May 24 16:29:14 2019 +0200 @@ -17,19 +17,13 @@ # with CubicWeb. If not, see . """Postgres specific store""" -from __future__ import print_function - import warnings import os.path as osp from io import StringIO from time import asctime from datetime import date, datetime, time from collections import defaultdict - -from six import string_types, integer_types, text_type, add_metaclass -from six.moves import cPickle as pickle, range - -from logilab.common.deprecation import class_deprecated +import pickle from cubicweb.utils import make_uid from cubicweb.server.sqlutils import SQL_PREFIX @@ -110,7 +104,7 @@ def _copyfrom_buffer_convert_number(value, **opts): '''Convert a number into its string representation''' - return text_type(value) + return str(value) def _copyfrom_buffer_convert_string(value, **opts): '''Convert string value. @@ -142,8 +136,8 @@ # (types, converter) list. _COPYFROM_BUFFER_CONVERTERS = [ (type(None), _copyfrom_buffer_convert_None), - (integer_types + (float,), _copyfrom_buffer_convert_number), - (string_types, _copyfrom_buffer_convert_string), + ((int, float), _copyfrom_buffer_convert_number), + (str, _copyfrom_buffer_convert_string), (datetime, _copyfrom_buffer_convert_datetime), (date, _copyfrom_buffer_convert_date), (time, _copyfrom_buffer_convert_time), @@ -187,7 +181,7 @@ for types, converter in _COPYFROM_BUFFER_CONVERTERS: if isinstance(value, types): value = converter(value, **convert_opts) - assert isinstance(value, text_type) + assert isinstance(value, str) break else: raise ValueError("Unsupported value type %s" % type(value)) @@ -198,75 +192,6 @@ return StringIO('\n'.join(rows)) -@add_metaclass(class_deprecated) -class SQLGenObjectStore(NoHookRQLObjectStore): - """Controller of the data import process. This version is based - on direct insertions throught SQL command (COPY FROM or execute many). - - >>> store = SQLGenObjectStore(cnx) - >>> store.create_entity('Person', ...) - >>> store.flush() - """ - __deprecation_warning__ = '[3.23] this class is deprecated, use MassiveObjectStore instead' - - def __init__(self, cnx, dump_output_dir=None, nb_threads_statement=1): - """ - Initialize a SQLGenObjectStore. - - Parameters: - - - cnx: connection on the cubicweb instance - - dump_output_dir: a directory to dump failed statements - for easier recovery. Default is None (no dump). - """ - super(SQLGenObjectStore, self).__init__(cnx) - ### hijack default source - self._system_source = SQLGenSourceWrapper( - self._system_source, cnx.vreg.schema, - dump_output_dir=dump_output_dir) - ### XXX This is done in super().__init__(), but should be - ### redone here to link to the correct source - self._add_relation = self._system_source.add_relation - self.indexes_etypes = {} - if nb_threads_statement != 1: - warnings.warn('[3.21] SQLGenObjectStore is no longer threaded', DeprecationWarning) - - def flush(self): - """Flush data to the database""" - self._system_source.flush() - - def relate(self, subj_eid, rtype, obj_eid, **kwargs): - if subj_eid is None or obj_eid is None: - return - # XXX Could subjtype be inferred ? - self._add_relation(self._cnx, subj_eid, rtype, obj_eid, - self.rschema(rtype).inlined, **kwargs) - if self.rschema(rtype).symmetric: - self._add_relation(self._cnx, obj_eid, rtype, subj_eid, - self.rschema(rtype).inlined, **kwargs) - - def drop_indexes(self, etype): - """Drop indexes for a given entity type""" - if etype not in self.indexes_etypes: - cu = self._cnx.cnxset.cu - def index_to_attr(index): - """turn an index name to (database) attribute name""" - return index.replace(etype.lower(), '').replace('idx', '').strip('_') - indices = [(index, index_to_attr(index)) - for index in self._system_source.dbhelper.list_indices(cu, etype) - # Do not consider 'cw_etype_pkey' index - if not index.endswith('key')] - self.indexes_etypes[etype] = indices - for index, attr in self.indexes_etypes[etype]: - self._cnx.system_sql('DROP INDEX %s' % index) - - def create_indexes(self, etype): - """Recreate indexes for a given entity type""" - for index, attr in self.indexes_etypes.get(etype, []): - sql = 'CREATE INDEX %s ON cw_%s(%s)' % (index, etype, attr) - self._cnx.system_sql(sql) - - ########################################################################### ## SQL Source ############################################################# ########################################################################### diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/stores.py --- a/cubicweb/dataimport/stores.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/stores.py Fri May 24 16:29:14 2019 +0200 @@ -58,17 +58,14 @@ .. autoclass:: cubicweb.dataimport.stores.MetadataGenerator """ import inspect -import warnings from datetime import datetime from copy import copy from itertools import count -from six import add_metaclass - import pytz from logilab.common.decorators import cached -from logilab.common.deprecation import deprecated, class_deprecated +from logilab.common.deprecation import class_deprecated from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES from cubicweb.server.edition import EditedEntity @@ -115,13 +112,9 @@ """Store that works by making RQL queries, hence with all the cubicweb's machinery activated. """ - def __init__(self, cnx, commit=None): - if commit is not None: - warnings.warn('[3.19] commit argument should not be specified ' - 'as the cnx object already provides it.', - DeprecationWarning, stacklevel=2) + def __init__(self, cnx): self._cnx = cnx - self._commit = commit or cnx.commit + self._commit = cnx.commit # XXX 3.21 deprecated attributes self.eids = {} self.types = {} @@ -149,23 +142,6 @@ def commit(self): return self._commit() - @deprecated("[3.19] use cnx.find(*args, **kwargs).entities() instead") - def find_entities(self, *args, **kwargs): - return self._cnx.find(*args, **kwargs).entities() - - @deprecated("[3.19] use cnx.find(*args, **kwargs).one() instead") - def find_one_entity(self, *args, **kwargs): - return self._cnx.find(*args, **kwargs).one() - - @deprecated('[3.21] use prepare_insert_entity instead') - def create_entity(self, *args, **kwargs): - eid = self.prepare_insert_entity(*args, **kwargs) - return self._cnx.entity_from_eid(eid) - - @deprecated('[3.21] use prepare_insert_relation instead') - def relate(self, eid_from, rtype, eid_to, **kwargs): - self.prepare_insert_relation(eid_from, rtype, eid_to, **kwargs) - class NoHookRQLObjectStore(RQLObjectStore): """Store that works by accessing low-level CubicWeb's source API, with all hooks deactivated. It @@ -212,7 +188,7 @@ self._system_source.add_info(cnx, entity, entity_source) self._system_source.add_entity(cnx, entity) kwargs = dict() - if inspect.getargspec(self._add_relation).keywords: + if inspect.getfullargspec(self._add_relation).varkw: kwargs['subjtype'] = entity.cw_etype for rtype, targeteids in rels.items(): # targeteids may be a single eid or a list of eids @@ -241,21 +217,6 @@ self._add_relation(self._cnx, eid_to, rtype, eid_from, rschema.inlined) self._nb_inserted_relations += 1 - @property - @deprecated('[3.21] deprecated') - def nb_inserted_entities(self): - return self._nb_inserted_entities - - @property - @deprecated('[3.21] deprecated') - def nb_inserted_types(self): - return self._nb_inserted_types - - @property - @deprecated('[3.21] deprecated') - def nb_inserted_relations(self): - return self._nb_inserted_relations - class MetadataGenerator(object): """Class responsible for generating standard metadata for imported entities. You may want to @@ -399,8 +360,7 @@ return self._mdgen.source -@add_metaclass(class_deprecated) -class MetaGenerator(object): +class MetaGenerator(object, metaclass=class_deprecated): """Class responsible for generating standard metadata for imported entities. You may want to derive it to add application specific's metadata. diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/test/test_pgstore.py --- a/cubicweb/dataimport/test/test_pgstore.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/dataimport/test/test_pgstore.py Fri May 24 16:29:14 2019 +0200 @@ -20,15 +20,9 @@ import datetime as DT -from six import PY3 from logilab.common.testlib import TestCase, unittest_main from cubicweb.dataimport import pgstore -from cubicweb.devtools import testlib - - -if PY3: - long = int class CreateCopyFromBufferTC(TestCase): @@ -42,7 +36,7 @@ def test_convert_number(self): cnvt = pgstore._copyfrom_buffer_convert_number self.assertEqual(u'42', cnvt(42)) - self.assertEqual(u'42', cnvt(long(42))) + self.assertEqual(u'42', cnvt(int(42))) self.assertEqual(u'42.42', cnvt(42.42)) def test_convert_string(self): @@ -69,9 +63,9 @@ # test buffer def test_create_copyfrom_buffer_tuple(self): - data = ((42, long(42), 42.42, u'éléphant', DT.date(666, 1, 13), DT.time(6, 6, 6), + data = ((42, int(42), 42.42, u'éléphant', DT.date(666, 1, 13), DT.time(6, 6, 6), DT.datetime(666, 6, 13, 6, 6, 6)), - (6, long(6), 6.6, u'babar', DT.date(2014, 1, 14), DT.time(4, 2, 1), + (6, int(6), 6.6, u'babar', DT.date(2014, 1, 14), DT.time(4, 2, 1), DT.datetime(2014, 1, 1, 0, 0, 0))) results = pgstore._create_copyfrom_buffer(data) # all columns @@ -94,15 +88,5 @@ self.assertEqual(expected, results.getvalue()) -class SQLGenObjectStoreTC(testlib.CubicWebTC): - - def test_prepare_insert_entity(self): - with self.admin_access.repo_cnx() as cnx: - store = pgstore.SQLGenObjectStore(cnx) - eid = store.prepare_insert_entity('CWUser', login=u'toto', - upassword=u'pwd') - self.assertIsNotNone(eid) - - if __name__ == '__main__': unittest_main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/dataimport/test/test_sqlgenstore.py --- a/cubicweb/dataimport/test/test_sqlgenstore.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr -- mailto:contact@logilab.fr -# -# This program is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with this program. If not, see . -"""SQL object store test case""" - -from cubicweb.dataimport import ucsvreader -from cubicweb.devtools import testlib, PostgresApptestConfiguration -from cubicweb.devtools import startpgcluster, stoppgcluster -from cubicweb.dataimport.pgstore import SQLGenObjectStore - - -def setUpModule(): - startpgcluster(__file__) - - -def tearDownModule(*args): - stoppgcluster(__file__) - - -class SQLGenImportSimpleTC(testlib.CubicWebTC): - configcls = PostgresApptestConfiguration - appid = 'data-massimport' - - def cast(self, _type, value): - try: - return _type(value) - except ValueError: - return None - - def push_geonames_data(self, dumpname, store): - # Push timezones - cnx = store._cnx - for code, gmt, dst, raw_offset in ucsvreader(open(self.datapath('timeZones.txt'), 'rb'), - delimiter='\t'): - cnx.create_entity('TimeZone', code=code, gmt=float(gmt), - dst=float(dst), raw_offset=float(raw_offset)) - timezone_code = dict(cnx.execute('Any C, X WHERE X is TimeZone, X code C')) - cnx.commit() - # Push data - for ind, infos in enumerate(ucsvreader(open(dumpname, 'rb'), - delimiter='\t', - ignore_errors=True)): - if ind > 99: - break - latitude = self.cast(float, infos[4]) - longitude = self.cast(float, infos[5]) - population = self.cast(int, infos[14]) - elevation = self.cast(int, infos[15]) - gtopo = self.cast(int, infos[16]) - feature_class = infos[6] - if len(infos[6]) != 1: - feature_class = None - entity = {'name': infos[1], - 'asciiname': infos[2], - 'alternatenames': infos[3], - 'latitude': latitude, 'longitude': longitude, - 'feature_class': feature_class, - 'alternate_country_code': infos[9], - 'admin_code_3': infos[12], - 'admin_code_4': infos[13], - 'population': population, 'elevation': elevation, - 'gtopo30': gtopo, 'timezone': timezone_code.get(infos[17]), - 'cwuri': u'http://sws.geonames.org/%s/' % int(infos[0]), - 'geonameid': int(infos[0]), - } - store.prepare_insert_entity('Location', **entity) - - def test_autoflush_metadata(self): - with self.admin_access.repo_cnx() as cnx: - crs = cnx.system_sql('SELECT * FROM entities WHERE type=%(t)s', - {'t': 'Location'}) - self.assertEqual(len(crs.fetchall()), 0) - store = SQLGenObjectStore(cnx) - store.prepare_insert_entity('Location', name=u'toto') - store.flush() - store.commit() - cnx.commit() - with self.admin_access.repo_cnx() as cnx: - crs = cnx.system_sql('SELECT * FROM entities WHERE type=%(t)s', - {'t': 'Location'}) - self.assertEqual(len(crs.fetchall()), 1) - - def test_sqlgenstore_etype_metadata(self): - with self.admin_access.repo_cnx() as cnx: - store = SQLGenObjectStore(cnx) - timezone_eid = store.prepare_insert_entity('TimeZone', code=u'12') - store.prepare_insert_entity('Location', timezone=timezone_eid) - store.flush() - store.commit() - eid, etname = cnx.execute('Any X, TN WHERE X timezone TZ, X is T, ' - 'T name TN')[0] - self.assertEqual(cnx.entity_from_eid(eid).cw_etype, etname) - - def test_simple_insert(self): - with self.admin_access.repo_cnx() as cnx: - store = SQLGenObjectStore(cnx) - self.push_geonames_data(self.datapath('geonames.csv'), store) - store.flush() - store.commit() - with self.admin_access.repo_cnx() as cnx: - rset = cnx.execute('Any X WHERE X is Location') - self.assertEqual(len(rset), 100) - rset = cnx.execute('Any X WHERE X is Location, X timezone T') - self.assertEqual(len(rset), 100) - - -if __name__ == '__main__': - import unittest - unittest.main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/__init__.py --- a/cubicweb/devtools/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """Test tools for cubicweb""" -from __future__ import print_function - import os import sys import errno @@ -31,18 +29,16 @@ from hashlib import sha1 # pylint: disable=E0611 from os.path import abspath, join, exists, split, isdir, dirname from functools import partial +import pickle -from six import text_type -from six.moves import cPickle as pickle +import filelock from logilab.common.decorators import cached, clear_cache from cubicweb import ExecutionError from cubicweb import schema, cwconfig from cubicweb.server.serverconfig import ServerConfiguration -from cubicweb.etwist.twconfig import WebConfigurationBase - -cwconfig.CubicWebConfiguration.cls_adjust_sys_path() +from cubicweb.web.webconfig import WebConfigurationBase # db auto-population configuration ############################################# @@ -94,7 +90,7 @@ DEFAULT_PSQL_SOURCES = DEFAULT_SOURCES.copy() DEFAULT_PSQL_SOURCES['system'] = DEFAULT_SOURCES['system'].copy() DEFAULT_PSQL_SOURCES['system']['db-driver'] = 'postgres' -DEFAULT_PSQL_SOURCES['system']['db-user'] = text_type(getpass.getuser()) +DEFAULT_PSQL_SOURCES['system']['db-user'] = getpass.getuser() DEFAULT_PSQL_SOURCES['system']['db-password'] = None # insert a dumb value as db-host to avoid unexpected connection to local server DEFAULT_PSQL_SOURCES['system']['db-host'] = 'REPLACEME' @@ -396,7 +392,7 @@ from cubicweb.repoapi import connect repo = self.get_repo() sources = self.config.read_sources_file() - login = text_type(sources['admin']['login']) + login = sources['admin']['login'] password = sources['admin']['password'] or 'xxx' cnx = connect(repo, login, password=password) return cnx @@ -427,11 +423,12 @@ raise ValueError('no initialization function for driver %r' % self.DRIVER) def has_cache(self, db_id): - """Check if a given database id exist in cb cache for the current config""" - cache_glob = self.absolute_backup_file('*', '*') - if cache_glob not in self.explored_glob: - self.discover_cached_db() - return self.db_cache_key(db_id) in self.db_cache + """Check if a given database id exist in db cache for the current config""" + key = self.db_cache_key(db_id) + if key in self.db_cache: + return True + self.discover_cached_db() + return key in self.db_cache def discover_cached_db(self): """Search available db_if for the current config""" @@ -469,20 +466,23 @@ ``pre_setup_func`` to setup the database. This function backup any database it build""" - if self.has_cache(test_db_id): - return # test_db_id, 'already in cache' - if test_db_id is DEFAULT_EMPTY_DB_ID: - self.init_test_database() - else: - print('Building %s for database %s' % (test_db_id, self.dbname)) - self.build_db_cache(DEFAULT_EMPTY_DB_ID) - self.restore_database(DEFAULT_EMPTY_DB_ID) - self.get_repo(startup=True) - cnx = self.get_cnx() - with cnx: - pre_setup_func(cnx, self.config) - cnx.commit() - self.backup_database(test_db_id) + lockfile = join(self._ensure_test_backup_db_dir(), + '{}.lock'.format(test_db_id)) + with filelock.FileLock(lockfile): + if self.has_cache(test_db_id): + return # test_db_id, 'already in cache' + if test_db_id is DEFAULT_EMPTY_DB_ID: + self.init_test_database() + else: + print('Building %s for database %s' % (test_db_id, self.dbname)) + self.build_db_cache(DEFAULT_EMPTY_DB_ID) + self.restore_database(DEFAULT_EMPTY_DB_ID) + self.get_repo(startup=True) + cnx = self.get_cnx() + with cnx: + pre_setup_func(cnx, self.config) + cnx.commit() + self.backup_database(test_db_id) class NoCreateDropDatabaseHandler(TestDataBaseHandler): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/dataimport.py --- a/cubicweb/devtools/dataimport.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# pylint: disable=W0614,W0401 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -from warnings import warn -warn('moved to cubicweb.dataimport', DeprecationWarning, stacklevel=2) -from cubicweb.dataimport import * diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/devctl.py --- a/cubicweb/devtools/devctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/devctl.py Fri May 24 16:29:14 2019 +0200 @@ -18,8 +18,6 @@ """additional cubicweb-ctl commands and command handlers for cubicweb and cubicweb's cubes development """ -from __future__ import print_function - # *ctl module should limit the number of import to be imported as quickly as # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash # completion). So import locally in command helpers. @@ -30,12 +28,9 @@ from datetime import datetime, date from os import getcwd, mkdir, chdir, path as osp import pkg_resources -from warnings import warn from pytz import UTC -from six.moves import input - from logilab.common import STD_BLACKLIST from logilab.common.modutils import clean_sys_modules from logilab.common.fileutils import ensure_fs_mode @@ -481,12 +476,7 @@ print('-> extracting messages:', end=' ') potfiles = [] # static messages - if osp.exists(osp.join('i18n', 'entities.pot')): - warn('entities.pot is deprecated, rename file ' - 'to static-messages.pot (%s)' - % osp.join('i18n', 'entities.pot'), DeprecationWarning) - potfiles.append(osp.join('i18n', 'entities.pot')) - elif osp.exists(osp.join('i18n', 'static-messages.pot')): + if osp.exists(osp.join('i18n', 'static-messages.pot')): potfiles.append(osp.join('i18n', 'static-messages.pot')) # messages from schema potfiles.append(self.schemapot()) @@ -723,7 +713,6 @@ longdesc = input( 'Enter a long description (leave empty to reuse the short one): ') dependencies = { - 'six': '>= 1.4.0', 'cubicweb': '>= %s' % cubicwebversion, } if verbose: @@ -797,7 +786,7 @@ continue try: rql, time = line.split('--') - rql = re.sub("(\'\w+': \d*)", '', rql) + rql = re.sub(r"(\'\w+': \d*)", '', rql) if '{' in rql: rql = rql[:rql.index('{')] req = requests.setdefault(rql, []) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/fake.py --- a/cubicweb/devtools/fake.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/fake.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from contextlib import contextmanager -from six import string_types - from logilab.database import get_db_helper from cubicweb.req import RequestSessionBase @@ -98,7 +96,7 @@ def set_request_header(self, header, value, raw=False): """set an incoming HTTP header (for test purpose only)""" - if isinstance(value, string_types): + if isinstance(value, str): value = [value] if raw: # adding encoded header is important, else page content diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/fill.py --- a/cubicweb/devtools/fill.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/fill.py Fri May 24 16:29:14 2019 +0200 @@ -17,9 +17,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """This modules defines func / methods for creating test repositories""" -from __future__ import print_function - - import logging from random import randint, choice @@ -28,9 +25,6 @@ from decimal import Decimal import inspect -from six import text_type, add_metaclass -from six.moves import range - from logilab.common import attrdict from logilab.mtconverter import xml_escape from yams.constraints import (SizeConstraint, StaticVocabularyConstraint, @@ -234,7 +228,7 @@ """ for cst in self.eschema.rdef(attrname).constraints: if isinstance(cst, StaticVocabularyConstraint): - return text_type(choice(cst.vocabulary())) + return choice(cst.vocabulary()) return None # XXX nothing to do here @@ -264,14 +258,13 @@ for attrname, attrvalue in classdict.items(): if callable(attrvalue): if attrname.startswith('generate_') and \ - len(inspect.getargspec(attrvalue).args) < 2: + len(inspect.getfullargspec(attrvalue).args) < 2: raise TypeError('generate_xxx must accept at least 1 argument') setattr(_ValueGenerator, attrname, attrvalue) return type.__new__(mcs, name, bases, classdict) -@add_metaclass(autoextend) -class ValueGenerator(_ValueGenerator): +class ValueGenerator(_ValueGenerator, metaclass=autoextend): pass @@ -359,7 +352,7 @@ fmt = vreg.property_value('ui.float-format') value = fmt % value else: - value = text_type(value) + value = value return entity diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/htmlparser.py --- a/cubicweb/devtools/htmlparser.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/htmlparser.py Fri May 24 16:29:14 2019 +0200 @@ -24,8 +24,6 @@ from lxml import etree -from logilab.common.deprecation import class_deprecated, class_renamed - from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE STRICT_DOCTYPE = str(STRICT_DOCTYPE) @@ -131,11 +129,6 @@ Validator.__init__(self) self.parser = etree.XMLParser() -SaxOnlyValidator = class_renamed('SaxOnlyValidator', - XMLValidator, - '[3.17] you should use the ' - 'XMLValidator class instead') - class XMLSyntaxValidator(Validator): """XML syntax validator, check XML is well-formed""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/httptest.py --- a/cubicweb/devtools/httptest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/httptest.py Fri May 24 16:29:14 2019 +0200 @@ -18,21 +18,14 @@ """this module contains base classes and utilities for integration with running http server """ -from __future__ import print_function - - +import http.client import random import threading import socket - -from six import PY3 -from six.moves import range, http_client -from six.moves.urllib.parse import urlparse - +from urllib.parse import urlparse from cubicweb.devtools.testlib import CubicWebTC -from cubicweb.devtools import ApptestConfiguration def get_available_port(ports_scan): @@ -54,7 +47,7 @@ for port in ports_scan: try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock = s.connect(("localhost", port)) + s.connect(("localhost", port)) except socket.error as err: if err.args[0] in (111, 106): return port @@ -64,7 +57,7 @@ class _CubicWebServerTC(CubicWebTC): - """Class for running a Twisted-based test web server. + """Base class for running a test web server. """ ports_range = range(7000, 8000) @@ -81,13 +74,13 @@ If no user is provided, admin connection are used. """ if user is None: - user = self.admlogin + user = self.admlogin passwd = self.admpassword if passwd is None: passwd = user response = self.web_get("login?__login=%s&__password=%s" % (user, passwd)) - assert response.status == http_client.SEE_OTHER, response.status + assert response.status == http.client.SEE_OTHER, response.status self._ident_cookie = response.getheader('Set-Cookie') assert self._ident_cookie return True @@ -95,11 +88,11 @@ def web_logout(self, user='admin', pwd=None): """Log out current http user""" if self._ident_cookie is not None: - response = self.web_get('logout') + self.web_get('logout') self._ident_cookie = None def web_request(self, path='', method='GET', body=None, headers=None): - """Return an http_client.HTTPResponse object for the specified path + """Return an http.client.HTTPResponse object for the specified path Use available credential if available. """ @@ -110,8 +103,8 @@ headers['Cookie'] = self._ident_cookie self._web_test_cnx.request(method, '/' + path, headers=headers, body=body) response = self._web_test_cnx.getresponse() - response.body = response.read() # to chain request - response.read = lambda : response.body + response.body = response.read() # to chain request + response.read = lambda: response.body return response def web_get(self, path='', body=None, headers=None): @@ -120,7 +113,7 @@ def setUp(self): super(_CubicWebServerTC, self).setUp() port = self.config['port'] or get_available_port(self.ports_range) - self.config.global_set_option('port', port) # force rewrite here + self.config.global_set_option('port', port) # force rewrite here self.config.global_set_option('base-url', 'http://127.0.0.1:%d/' % port) # call load_configuration again to let the config reset its datadir_url self.config.load_configuration() @@ -133,55 +126,9 @@ class CubicWebServerTC(_CubicWebServerTC): def start_server(self): - if PY3: - self.skipTest('not using twisted on python3') - from twisted.internet import reactor - from cubicweb.etwist.server import run - # use a semaphore to avoid starting test while the http server isn't - # fully initilialized - semaphore = threading.Semaphore(0) - def safe_run(*args, **kwargs): - try: - run(*args, **kwargs) - finally: - semaphore.release() - - reactor.addSystemEventTrigger('after', 'startup', semaphore.release) - t = threading.Thread(target=safe_run, name='cubicweb_test_web_server', - args=(self.config, True), kwargs={'repo': self.repo}) - self.web_thread = t - t.start() - semaphore.acquire() - if not self.web_thread.isAlive(): - # XXX race condition with actual thread death - raise RuntimeError('Could not start the web server') - #pre init utils connection - parseurl = urlparse(self.config['base-url']) - assert parseurl.port == self.config['port'], (self.config['base-url'], self.config['port']) - self._web_test_cnx = http_client.HTTPConnection(parseurl.hostname, - parseurl.port) - self._ident_cookie = None - - def stop_server(self, timeout=15): - """Stop the webserver, waiting for the thread to return""" - from twisted.internet import reactor - if self._web_test_cnx is None: - self.web_logout() - self._web_test_cnx.close() - try: - reactor.stop() - self.web_thread.join(timeout) - assert not self.web_thread.isAlive() - - finally: - reactor.__init__() - - -class CubicWebWsgiTC(CubicWebServerTC): - def start_server(self): from cubicweb.wsgi.handler import CubicWebWSGIApplication from wsgiref import simple_server - from six.moves import queue + import queue config = self.config port = config['port'] or 8080 @@ -214,7 +161,7 @@ self.fail(start_flag.get()) parseurl = urlparse(self.config['base-url']) assert parseurl.port == self.config['port'], (self.config['base-url'], self.config['port']) - self._web_test_cnx = http_client.HTTPConnection(parseurl.hostname, + self._web_test_cnx = http.client.HTTPConnection(parseurl.hostname, parseurl.port) self._ident_cookie = None diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/instrument.py --- a/cubicweb/devtools/instrument.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/instrument.py Fri May 24 16:29:14 2019 +0200 @@ -14,8 +14,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with this program. If not, see . """Instrumentation utilities""" -from __future__ import print_function - import os try: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/qunit.py --- a/cubicweb/devtools/qunit.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/qunit.py Fri May 24 16:29:14 2019 +0200 @@ -15,16 +15,13 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from __future__ import absolute_import, print_function - import os, os.path as osp import errno import shutil -from tempfile import mkdtemp +from queue import Queue, Empty +from tempfile import mkdtemp, TemporaryDirectory from subprocess import Popen, PIPE, STDOUT -from six.moves.queue import Queue, Empty - # imported by default to simplify further import statements from logilab.common.testlib import Tags import webtest.http @@ -34,7 +31,6 @@ from cubicweb.web.controller import Controller from cubicweb.web.views.staticcontrollers import StaticFileController, STATIC_CONTROLLERS from cubicweb.devtools import webtest as cwwebtest -from cubicweb.devtools.testlib import TemporaryDirectory class FirefoxHelper(object): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/repotest.py --- a/cubicweb/devtools/repotest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/repotest.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ This module contains functions to initialize a new repository. """ -from __future__ import print_function - from contextlib import contextmanager from pprint import pprint diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/stresstester.py --- a/cubicweb/devtools/stresstester.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/stresstester.py Fri May 24 16:29:14 2019 +0200 @@ -41,8 +41,6 @@ Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. http://www.logilab.fr/ -- mailto:contact@logilab.fr """ -from __future__ import print_function - import os import sys import threading diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/data/cubes/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/data/cubes/i18ntestcube --- a/cubicweb/devtools/test/data/cubes/i18ntestcube Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_i18ntestcube/ \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_dbfill.py --- a/cubicweb/devtools/test/unittest_dbfill.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_dbfill.py Fri May 24 16:29:14 2019 +0200 @@ -23,8 +23,6 @@ import datetime import io -from six.moves import range - from logilab.common.testlib import TestCase, unittest_main from cubicweb.devtools.fill import ValueGenerator, make_tel diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_devctl.py --- a/cubicweb/devtools/test/unittest_devctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_devctl.py Fri May 24 16:29:14 2019 +0200 @@ -21,10 +21,9 @@ import os.path as osp import sys from subprocess import Popen, PIPE, STDOUT +from tempfile import TemporaryDirectory from unittest import TestCase -from cubicweb.devtools.testlib import TemporaryDirectory - def newcube(directory, name): cmd = ['cubicweb-ctl', 'newcube', '--directory', directory, name] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_httptest.py --- a/cubicweb/devtools/test/unittest_httptest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_httptest.py Fri May 24 16:29:14 2019 +0200 @@ -17,90 +17,50 @@ # with CubicWeb. If not, see . """unittest for cubicweb.devtools.httptest module""" -from six.moves import http_client +import http.client from logilab.common.testlib import Tags -from cubicweb.devtools.httptest import CubicWebServerTC, CubicWebWsgiTC +from cubicweb.devtools.httptest import CubicWebServerTC -class TwistedCWAnonTC(CubicWebServerTC): +class WsgiCWAnonTC(CubicWebServerTC): def test_response(self): try: response = self.web_get() - except http_client.NotConnected as ex: + except http.client.NotConnected as ex: self.fail("Can't connection to test server: %s" % ex) def test_response_anon(self): response = self.web_get() - self.assertEqual(response.status, http_client.OK) + self.assertEqual(response.status, http.client.OK) def test_base_url(self): if self.config['base-url'] not in self.web_get().read().decode('ascii'): self.fail('no mention of base url in retrieved page') -class TwistedCWIdentTC(CubicWebServerTC): +class WsgiCWIdentTC(CubicWebServerTC): test_db_id = 'httptest-cwident' anonymous_allowed = False tags = CubicWebServerTC.tags | Tags(('auth',)) def test_response_denied(self): response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN) + self.assertEqual(response.status, http.client.FORBIDDEN) def test_login(self): response = self.web_get() - if response.status != http_client.FORBIDDEN: + if response.status != http.client.FORBIDDEN: self.skipTest('Already authenticated, "test_response_denied" must have failed') # login self.web_login(self.admlogin, self.admpassword) response = self.web_get() - self.assertEqual(response.status, http_client.OK, response.body) + self.assertEqual(response.status, http.client.OK, response.body) # logout self.web_logout() response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN, response.body) - - -class WsgiCWAnonTC(CubicWebWsgiTC): - - def test_response(self): - try: - response = self.web_get() - except http_client.NotConnected as ex: - self.fail("Can't connection to test server: %s" % ex) - - def test_response_anon(self): - response = self.web_get() - self.assertEqual(response.status, http_client.OK) - - def test_base_url(self): - if self.config['base-url'] not in self.web_get().read().decode('ascii'): - self.fail('no mention of base url in retrieved page') - - -class WsgiCWIdentTC(CubicWebWsgiTC): - test_db_id = 'httptest-cwident' - anonymous_allowed = False - tags = CubicWebServerTC.tags | Tags(('auth',)) - - def test_response_denied(self): - response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN) - - def test_login(self): - response = self.web_get() - if response.status != http_client.FORBIDDEN: - self.skipTest('Already authenticated, "test_response_denied" must have failed') - # login - self.web_login(self.admlogin, self.admpassword) - response = self.web_get() - self.assertEqual(response.status, http_client.OK, response.body) - # logout - self.web_logout() - response = self.web_get() - self.assertEqual(response.status, http_client.FORBIDDEN, response.body) + self.assertEqual(response.status, http.client.FORBIDDEN, response.body) if __name__ == '__main__': diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_i18n.py --- a/cubicweb/devtools/test/unittest_i18n.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_i18n.py Fri May 24 16:29:14 2019 +0200 @@ -19,15 +19,13 @@ """unit tests for i18n messages generator""" from contextlib import contextmanager -from io import StringIO, BytesIO +from io import StringIO import os import os.path as osp import sys from subprocess import PIPE, Popen, STDOUT from unittest import TestCase, main - -from six import PY2 -from mock import patch +from unittest.mock import patch from cubicweb.devtools import devctl from cubicweb.devtools.testlib import BaseTestCase @@ -58,7 +56,7 @@ return msgs -TESTCUBE_DIR = osp.join(DATADIR, 'cubes', 'i18ntestcube') +TESTCUBE_DIR = osp.join(DATADIR, 'libpython', 'cubicweb_i18ntestcube') class cubePotGeneratorTC(TestCase): @@ -74,17 +72,6 @@ cubedir = osp.join(DATADIR, 'libpython', 'cubicweb_i18ntestcube') self._check(cubedir, env) - def test_i18ncube_legacy_layout(self): - env = os.environ.copy() - env['CW_CUBES_PATH'] = osp.join(DATADIR, 'cubes') - if 'PYTHONPATH' in env: - env['PYTHONPATH'] += os.pathsep - else: - env['PYTHONPATH'] = '' - env['PYTHONPATH'] += DATADIR - cubedir = osp.join(DATADIR, 'cubes', 'i18ntestcube') - self._check(cubedir, env) - def _check(self, cubedir, env): cmd = [sys.executable, '-m', 'cubicweb', 'i18ncube', 'i18ntestcube'] proc = Popen(cmd, env=env, stdout=PIPE, stderr=STDOUT) @@ -102,7 +89,7 @@ @contextmanager def capture_stdout(): - stream = BytesIO() if PY2 else StringIO() + stream = StringIO() sys.stdout = stream yield stream stream.seek(0) @@ -137,19 +124,14 @@ @patch('pkg_resources.load_entry_point', return_value=FakeMessageExtractor) def test_cube_custom_extractor(self, mock_load_entry_point): distname = 'cubicweb_i18ntestcube' # same for new and legacy layout - for cubedir in [ - osp.join(DATADIR, 'libpython', 'cubicweb_i18ntestcube'), - # Legacy cubes. - osp.join(DATADIR, 'cubes', 'i18ntestcube'), - ]: - with self.subTest(cubedir=cubedir): - with capture_stdout() as stream: - devctl.update_cube_catalogs(cubedir) - self.assertIn(u'no message catalog for cube i18ntestcube', - stream.read()) - mock_load_entry_point.assert_called_once_with( - distname, 'cubicweb.i18ncube', 'i18ntestcube') - mock_load_entry_point.reset_mock() + cubedir = osp.join(DATADIR, 'libpython', 'cubicweb_i18ntestcube') + with capture_stdout() as stream: + devctl.update_cube_catalogs(cubedir) + self.assertIn(u'no message catalog for cube i18ntestcube', + stream.read()) + mock_load_entry_point.assert_called_once_with( + distname, 'cubicweb.i18ncube', 'i18ntestcube') + mock_load_entry_point.reset_mock() if __name__ == '__main__': diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_testlib.py --- a/cubicweb/devtools/test/unittest_testlib.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_testlib.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from io import BytesIO, StringIO from unittest import TextTestRunner -from six import PY2 - from logilab.common.testlib import TestSuite, TestCase, unittest_main from logilab.common.registry import yes @@ -52,7 +50,7 @@ class WebTestTC(TestCase): def setUp(self): - output = BytesIO() if PY2 else StringIO() + output = StringIO() self.runner = TextTestRunner(stream=output) def test_error_raised(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/test/unittest_webtest.py --- a/cubicweb/devtools/test/unittest_webtest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/test/unittest_webtest.py Fri May 24 16:29:14 2019 +0200 @@ -1,4 +1,4 @@ -from six.moves import http_client +import http.client from logilab.common.testlib import Tags from cubicweb.devtools.webtest import CubicWebTestTC @@ -21,19 +21,19 @@ def test_reponse_denied(self): res = self.webapp.get('/', expect_errors=True) - self.assertEqual(http_client.FORBIDDEN, res.status_int) + self.assertEqual(http.client.FORBIDDEN, res.status_int) def test_login(self): res = self.webapp.get('/', expect_errors=True) - self.assertEqual(http_client.FORBIDDEN, res.status_int) + self.assertEqual(http.client.FORBIDDEN, res.status_int) self.login(self.admlogin, self.admpassword) res = self.webapp.get('/') - self.assertEqual(http_client.OK, res.status_int) + self.assertEqual(http.client.OK, res.status_int) self.logout() res = self.webapp.get('/', expect_errors=True) - self.assertEqual(http_client.FORBIDDEN, res.status_int) + self.assertEqual(http.client.FORBIDDEN, res.status_int) if __name__ == '__main__': diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/devtools/testlib.py --- a/cubicweb/devtools/testlib.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/devtools/testlib.py Fri May 24 16:29:14 2019 +0200 @@ -17,21 +17,15 @@ # with CubicWeb. If not, see . """Base classes and utilities for cubicweb tests""" -from __future__ import print_function - import sys import re -import warnings from os.path import dirname, join, abspath from math import log from contextlib import contextmanager from inspect import isgeneratorfunction from itertools import chain -from warnings import warn - -from six import binary_type, text_type, string_types, reraise -from six.moves import range -from six.moves.urllib.parse import urlparse, parse_qs, unquote as urlunquote +from unittest import TestCase +from urllib.parse import urlparse, parse_qs, unquote as urlunquote import yams.schema @@ -39,7 +33,7 @@ from logilab.common.debugger import Debugger from logilab.common.umessage import message_from_string from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod -from logilab.common.deprecation import deprecated, class_deprecated +from logilab.common.deprecation import class_deprecated from logilab.common.shellutils import getlogin from cubicweb import (ValidationError, NoSelectableObject, AuthenticationError, @@ -54,22 +48,6 @@ from cubicweb.devtools.fill import insert_entity_queries, make_relations_queries from cubicweb.web.views.authentication import Session -if sys.version_info[:2] < (3, 4): - from unittest2 import TestCase - if not hasattr(TestCase, 'subTest'): - raise ImportError('no subTest support in available unittest2') - try: - from backports.tempfile import TemporaryDirectory # noqa - except ImportError: - # backports.tempfile not available - TemporaryDirectory = None -else: - from unittest import TestCase - from tempfile import TemporaryDirectory # noqa - -# in python 2.7, DeprecationWarning are not shown anymore by default -warnings.filterwarnings('default', category=DeprecationWarning) - # provide a data directory for the test class ################################## @@ -327,7 +305,6 @@ """provide a new RepoAccess object for a given user The access is automatically closed at the end of the test.""" - login = text_type(login) access = RepoAccess(self.repo, login, self.requestcls) self._open_access.add(access) return access @@ -348,7 +325,7 @@ db_handler.restore_database(self.test_db_id) self.repo = db_handler.get_repo(startup=True) # get an admin session (without actual login) - login = text_type(db_handler.config.default_admin_config['login']) + login = db_handler.config.default_admin_config['login'] self.admin_access = self.new_access(login) # config management ######################################################## @@ -366,7 +343,7 @@ been properly bootstrapped. """ admincfg = config.default_admin_config - cls.admlogin = text_type(admincfg['login']) + cls.admlogin = admincfg['login'] cls.admpassword = admincfg['password'] # uncomment the line below if you want rql queries to be logged # config.global_set_option('query-log-file', @@ -453,29 +430,19 @@ # user / session management ############################################### - @deprecated('[3.19] explicitly use RepoAccess object in test instead') - def user(self, req=None): - """return the application schema""" - if req is None: - return self.request().user - else: - return req.user - @iclassmethod # XXX turn into a class method def create_user(self, req, login=None, groups=('users',), password=None, email=None, commit=True, **kwargs): """create and return a new user entity""" if password is None: password = login - if login is not None: - login = text_type(login) user = req.create_entity('CWUser', login=login, upassword=password, **kwargs) req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)' % ','.join(repr(str(g)) for g in groups), {'x': user.eid}) if email is not None: - req.create_entity('EmailAddress', address=text_type(email), + req.create_entity('EmailAddress', address=email, reverse_primary_email=user) user.cw_clear_relation_cache('in_group', 'subject') if commit: @@ -530,7 +497,7 @@ """ torestore = [] for erschema, etypeperms in chain(perm_overrides, perm_kwoverrides.items()): - if isinstance(erschema, string_types): + if isinstance(erschema, str): erschema = self.schema[erschema] for action, actionperms in etypeperms.items(): origperms = erschema.permissions[action] @@ -668,15 +635,6 @@ publisher.error_handler = raise_error_handler return publisher - @deprecated('[3.19] use the .remote_calling method') - def remote_call(self, fname, *args): - """remote json call simulation""" - dump = json.dumps - args = [dump(arg) for arg in args] - req = self.request(fname=fname, pageid='123', arg=args) - ctrl = self.vreg['controllers'].select('ajax', req) - return ctrl.publish(), req - @contextmanager def remote_calling(self, fname, *args, **kwargs): """remote json call simulation""" @@ -685,19 +643,9 @@ ctrl = self.vreg['controllers'].select('ajax', req) yield ctrl.publish(), req - def app_handle_request(self, req, path=None): - if path is not None: - warn('[3.24] path argument got removed from app_handle_request parameters, ' - 'give it to the request constructor', DeprecationWarning) - if req.relative_path(False) != path: - req._url = path + def app_handle_request(self, req): return self.app.core_handle(req) - @deprecated("[3.15] app_handle_request is the new and better way" - " (beware of small semantic changes)") - def app_publish(self, *args, **kwargs): - return self.app_handle_request(*args, **kwargs) - def ctrl_publish(self, req, ctrl='edit', rset=None): """call the publish method of the edit controller""" ctrl = self.vreg['controllers'].select(ctrl, req, appli=self.app) @@ -748,20 +696,6 @@ form['_cw_fields'] = ','.join(sorted(fields)) return form - @deprecated('[3.19] use .admin_request_from_url instead') - def req_from_url(self, url): - """parses `url` and builds the corresponding CW-web request - - req.form will be setup using the url's query string - """ - req = self.request(url=url) - if isinstance(url, text_type): - url = url.encode(req.encoding) # req.setup_params() expects encoded strings - querystring = urlparse(url)[-2] - params = parse_qs(querystring) - req.setup_params(params) - return req - @contextmanager def admin_request_from_url(self, url): """parses `url` and builds the corresponding CW-web request @@ -769,7 +703,7 @@ req.form will be setup using the url's query string """ with self.admin_access.web_request(url=url) as req: - if isinstance(url, text_type): + if isinstance(url, str): url = url.encode(req.encoding) # req.setup_params() expects encoded strings querystring = urlparse(url)[-2] params = parse_qs(querystring) @@ -841,11 +775,6 @@ location = req.get_response_header('location') return self._parse_location(req, location) - @deprecated("[3.15] expect_redirect_handle_request is the new and better way" - " (beware of small semantic changes)") - def expect_redirect_publish(self, *args, **kwargs): - return self.expect_redirect_handle_request(*args, **kwargs) - def set_auth_mode(self, authmode, anonuser=None): self.set_option('auth-mode', authmode) self.set_option('anonymous-user', anonuser) @@ -955,7 +884,7 @@ msg = '[%s in %s] %s' % (klass, view.__regid__, exc) except Exception: msg = '[%s in %s] undisplayable exception' % (klass, view.__regid__) - reraise(AssertionError, AssertionError(msg), sys.exc_info()[-1]) + raise AssertionError(msg).with_traceback(sys.exc_info()[-1]) return self._check_html(output, view, template) def get_validator(self, view=None, content_type=None, output=None): @@ -988,7 +917,7 @@ def _check_html(self, output, view, template='main-template'): """raises an exception if the HTML is invalid""" output = output.strip() - if isinstance(output, text_type): + if isinstance(output, str): # XXX output = output.encode('utf-8') validator = self.get_validator(view, output=output) @@ -1021,8 +950,8 @@ position = getattr(exc, "position", (0,))[0] if position: # define filter - if isinstance(content, binary_type): - content = text_type(content, sys.getdefaultencoding(), 'replace') + if isinstance(content, bytes): + content = str(content, sys.getdefaultencoding(), 'replace') content = validator.preprocess_data(content) content = content.splitlines() width = int(log(len(content), 10)) + 1 diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/__init__.py --- a/cubicweb/entities/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -17,14 +17,7 @@ # with CubicWeb. If not, see . """base application's entities class implementation: `AnyEntity`""" - - -from warnings import warn - -from six import text_type, string_types - from logilab.common.decorators import classproperty -from logilab.common.deprecation import deprecated from cubicweb import Unauthorized from cubicweb.entity import Entity @@ -39,7 +32,7 @@ @classproperty def cw_etype(cls): """entity type as a unicode string""" - return text_type(cls.__regid__) + return cls.__regid__ @classmethod def cw_create_url(cls, req, **kwargs): @@ -47,36 +40,11 @@ return req.build_url('add/%s' % cls.__regid__, **kwargs) @classmethod - @deprecated('[3.22] use cw_fti_index_rql_limit instead') - def cw_fti_index_rql_queries(cls, req): - """return the list of rql queries to fetch entities to FT-index - - The default is to fetch all entities at once and to prefetch - indexable attributes but one could imagine iterating over - "smaller" resultsets if the table is very big or returning - a subset of entities that match some business-logic condition. - """ - restrictions = ['X is %s' % cls.__regid__] - selected = ['X'] - for attrschema in sorted(cls.e_schema.indexable_attributes()): - varname = attrschema.type.upper() - restrictions.append('X %s %s' % (attrschema, varname)) - selected.append(varname) - return ['Any %s WHERE %s' % (', '.join(selected), - ', '.join(restrictions))] - - @classmethod def cw_fti_index_rql_limit(cls, req, limit=1000): """generate rsets of entities to FT-index By default, each successive result set is limited to 1000 entities """ - if cls.cw_fti_index_rql_queries.__func__ != AnyEntity.cw_fti_index_rql_queries.__func__: - warn("[3.22] cw_fti_index_rql_queries is replaced by cw_fti_index_rql_limit", - DeprecationWarning) - for rql in cls.cw_fti_index_rql_queries(req): - yield req.execute(rql) - return restrictions = ['X is %s' % cls.__regid__] selected = ['X'] start = 0 @@ -141,8 +109,8 @@ if rtype is None: return self.dc_title().lower() value = self.cw_attr_value(rtype) - # do not restrict to `unicode` because Bytes will return a `str` value - if isinstance(value, string_types): + # do not restrict to `str` because Bytes will return a `str` value + if isinstance(value, str): return self.printable_value(rtype, format='text/plain').lower() return value diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/adapters.py --- a/cubicweb/entities/adapters.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/adapters.py Fri May 24 16:29:14 2019 +0200 @@ -27,6 +27,7 @@ from cubicweb import (Unauthorized, ValidationError, view, ViolatedConstraint, UniqueTogetherError) +from cubicweb.schema import constraint_name_for from cubicweb.predicates import is_instance, relation_possible, match_exception @@ -474,7 +475,7 @@ for rschema, attrschema in eschema.attribute_definitions(): rdef = rschema.rdef(eschema, attrschema) for constraint in rdef.constraints: - if cstrname == constraint.name_for(rdef): + if cstrname == constraint_name_for(constraint, rdef): break else: continue diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/authobjs.py --- a/cubicweb/entities/authobjs.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/authobjs.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """entity classes user and group entities""" -from six import string_types, text_type - from logilab.common.decorators import cached from cubicweb import Unauthorized @@ -110,13 +108,12 @@ return self._cw.vreg.property_value(key) def set_property(self, pkey, value): - value = text_type(value) try: prop = self._cw.execute( 'CWProperty X WHERE X pkey %(k)s, X for_user U, U eid %(u)s', {'k': pkey, 'u': self.eid}).get_entity(0, 0) except Exception: - kwargs = dict(pkey=text_type(pkey), value=value) + kwargs = dict(pkey=pkey, value=value) if self.is_in_group('managers'): kwargs['for_user'] = self self._cw.create_entity('CWProperty', **kwargs) @@ -129,7 +126,7 @@ :type groups: str or iterable(str) :param groups: a group name or an iterable on group names """ - if isinstance(groups, string_types): + if isinstance(groups, str): groups = frozenset((groups,)) elif isinstance(groups, (tuple, list)): groups = frozenset(groups) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/lib.py --- a/cubicweb/entities/lib.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/lib.py Fri May 24 16:29:14 2019 +0200 @@ -20,9 +20,7 @@ from warnings import warn from datetime import datetime - -from six.moves import range -from six.moves.urllib.parse import urlsplit, urlunsplit +from urllib.parse import urlsplit, urlunsplit from logilab.mtconverter import xml_escape @@ -125,25 +123,3 @@ return self._cw._(self._cw.vreg.property_info(self.pkey)['help']) except UnknownProperty: return u'' - - -class CWCache(AnyEntity): - """Cache""" - __regid__ = 'CWCache' - fetch_attrs, cw_fetch_order = fetch_config(['name']) - - def __init__(self, *args, **kwargs): - warn('[3.19] CWCache entity type is going away soon. ' - 'Other caching mechanisms can be used more reliably ' - 'to the same effect.', - DeprecationWarning) - super(CWCache, self).__init__(*args, **kwargs) - - def touch(self): - self._cw.execute('SET X timestamp %(t)s WHERE X eid %(x)s', - {'t': datetime.now(), 'x': self.eid}) - - def valid(self, date): - if date: - return date > self.timestamp - return False diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/sources.py --- a/cubicweb/entities/sources.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/sources.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ from socket import gethostname import logging -from six import text_type - from logilab.common.textutils import text_to_dict from logilab.common.configuration import OptionError from logilab.mtconverter import xml_escape diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/test/unittest_base.py --- a/cubicweb/entities/test/unittest_base.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/test/unittest_base.py Fri May 24 16:29:14 2019 +0200 @@ -21,7 +21,6 @@ from logilab.common.testlib import unittest_main from logilab.common.decorators import clear_cache -from logilab.common.registry import yes from cubicweb.devtools.testlib import CubicWebTC @@ -65,33 +64,13 @@ {'description_format': ('format', 'description')}) def test_fti_rql_method(self): - class EmailAddress(AnyEntity): - __regid__ = 'EmailAddress' - __select__ = AnyEntity.__select__ & yes(2) - - @classmethod - def cw_fti_index_rql_queries(cls, req): - return ['EmailAddress Y'] - with self.admin_access.web_request() as req: req.create_entity('EmailAddress', address=u'foo@bar.com') eclass = self.vreg['etypes'].etype_class('EmailAddress') - # deprecated - self.assertEqual(['Any X, ADDRESS, ALIAS WHERE X is EmailAddress, ' - 'X address ADDRESS, X alias ALIAS'], - eclass.cw_fti_index_rql_queries(req)) - self.assertEqual(['Any X, ADDRESS, ALIAS ORDERBY X LIMIT 1000 WHERE X is EmailAddress, ' 'X address ADDRESS, X alias ALIAS, X eid > 0'], [rset.rql for rset in eclass.cw_fti_index_rql_limit(req)]) - # test backwards compatibility with custom method - with self.temporary_appobjects(EmailAddress): - self.vreg['etypes'].clear_caches() - eclass = self.vreg['etypes'].etype_class('EmailAddress') - self.assertEqual(['EmailAddress Y'], - [rset.rql for rset in eclass.cw_fti_index_rql_limit(req)]) - class EmailAddressTC(BaseEntityTC): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/test/unittest_wfobjs.py --- a/cubicweb/entities/test/unittest_wfobjs.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/test/unittest_wfobjs.py Fri May 24 16:29:14 2019 +0200 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from cubicweb import ValidationError +from cubicweb import _, ValidationError from cubicweb.devtools.testlib import CubicWebTC def add_wf(shell, etype, name=None, default=False): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entities/wfobjs.py --- a/cubicweb/entities/wfobjs.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entities/wfobjs.py Fri May 24 16:29:14 2019 +0200 @@ -21,14 +21,7 @@ * workflow history (TrInfo) * adapter for workflowable entities (IWorkflowableAdapter) """ -from __future__ import print_function - - - -from six import text_type, string_types - from logilab.common.decorators import cached, clear_cache -from logilab.common.deprecation import deprecated from cubicweb.entities import AnyEntity, fetch_config from cubicweb.view import EntityAdapter @@ -99,7 +92,7 @@ def transition_by_name(self, trname): rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, ' 'T transition_of WF, WF eid %(wf)s', - {'n': text_type(trname), 'wf': self.eid}) + {'n': trname, 'wf': self.eid}) if rset: return rset.get_entity(0, 0) return None @@ -116,7 +109,7 @@ def add_state(self, name, initial=False, **kwargs): """add a state to this workflow""" - state = self._cw.create_entity('State', name=text_type(name), **kwargs) + state = self._cw.create_entity('State', name=name, **kwargs) self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s', {'s': state.eid, 'wf': self.eid}) if initial: @@ -128,7 +121,7 @@ def _add_transition(self, trtype, name, fromstates, requiredgroups=(), conditions=(), **kwargs): - tr = self._cw.create_entity(trtype, name=text_type(name), **kwargs) + tr = self._cw.create_entity(trtype, name=name, **kwargs) self._cw.execute('SET T transition_of WF ' 'WHERE T eid %(t)s, WF eid %(wf)s', {'t': tr.eid, 'wf': self.eid}) @@ -258,13 +251,13 @@ for gname in requiredgroups: rset = self._cw.execute('SET T require_group G ' 'WHERE T eid %(x)s, G name %(gn)s', - {'x': self.eid, 'gn': text_type(gname)}) + {'x': self.eid, 'gn': gname}) assert rset, '%s is not a known group' % gname - if isinstance(conditions, string_types): + if isinstance(conditions, str): conditions = (conditions,) for expr in conditions: - if isinstance(expr, string_types): - kwargs = {'expr': text_type(expr)} + if isinstance(expr, str): + kwargs = {'expr': expr} else: assert isinstance(expr, dict) kwargs = expr @@ -416,7 +409,7 @@ """return the default workflow for entities of this type""" # XXX CWEType method wfrset = self._cw.execute('Any WF WHERE ET default_workflow WF, ' - 'ET name %(et)s', {'et': text_type(self.entity.cw_etype)}) + 'ET name %(et)s', {'et': self.entity.cw_etype}) if wfrset: return wfrset.get_entity(0, 0) self.warning("can't find any workflow for %s", self.entity.cw_etype) @@ -481,7 +474,7 @@ 'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, ' 'T type TT, T type %(type)s, ' 'T name TN, T transition_of WF, WF eid %(wfeid)s', - {'x': self.current_state.eid, 'type': text_type(type), + {'x': self.current_state.eid, 'type': type, 'wfeid': self.current_workflow.eid}) for tr in rset.entities(): if tr.may_be_fired(self.entity.eid): @@ -530,7 +523,7 @@ def _get_transition(self, tr): assert self.current_workflow - if isinstance(tr, string_types): + if isinstance(tr, str): _tr = self.current_workflow.transition_by_name(tr) assert _tr is not None, 'not a %s transition: %s' % ( self.__regid__, tr) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/entity.py --- a/cubicweb/entity.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/entity.py Fri May 24 16:29:14 2019 +0200 @@ -17,13 +17,7 @@ # with CubicWeb. If not, see . """Base class for entity objects manipulated in clients""" -from warnings import warn - -from six import text_type, string_types, integer_types -from six.moves import range - from logilab.common.decorators import cached -from logilab.common.deprecation import deprecated from logilab.common.registry import yes from logilab.mtconverter import TransformData, xml_escape @@ -58,7 +52,6 @@ """return True if value can be used at the end of a Rest URL path""" if value is None: return False - value = text_type(value) # the check for ?, /, & are to prevent problems when running # behind Apache mod_proxy if value == u'' or u'?' in value or u'/' in value or u'&' in value: @@ -175,7 +168,6 @@ # class attributes that must be set in class definition rest_attr = None fetch_attrs = None - skip_copy_for = () # bw compat (< 3.14), use cw_skip_copy_for instead cw_skip_copy_for = [('in_state', 'subject')] # class attributes set automatically at registration time e_schema = None @@ -256,23 +248,11 @@ select.add_sort_var(var, asc=False) @classmethod - def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', + def fetch_rql(cls, user, fetchattrs=None, mainvar='X', settype=True, ordermethod='fetch_order'): st = cls.fetch_rqlst(user, mainvar=mainvar, fetchattrs=fetchattrs, settype=settype, ordermethod=ordermethod) - rql = st.as_string() - if restriction: - # cannot use RQLRewriter API to insert 'X rtype %(x)s' restriction - warn('[3.14] fetch_rql: use of `restriction` parameter is ' - 'deprecated, please use fetch_rqlst and supply a syntax' - 'tree with your restriction instead', DeprecationWarning) - insert = ' WHERE ' + ','.join(restriction) - if ' WHERE ' in rql: - select, where = rql.split(' WHERE ', 1) - rql = select + insert + ',' + where - else: - rql += insert - return rql + return st.as_string() @classmethod def fetch_rqlst(cls, user, select=None, mainvar='X', fetchattrs=None, @@ -281,7 +261,7 @@ select = Select() mainvar = select.get_variable(mainvar) select.add_selected(mainvar) - elif isinstance(mainvar, string_types): + elif isinstance(mainvar, str): assert mainvar in select.defined_vars mainvar = select.get_variable(mainvar) # eases string -> syntax tree test transition: please remove once stable @@ -377,34 +357,8 @@ etypecls._fetch_restrictions(var, select, fetchattrs, user, None, visited=visited) if ordermethod is not None: - try: - cmeth = getattr(cls, ordermethod) - warn('[3.14] %s %s class method should be renamed to cw_%s' - % (cls.__regid__, ordermethod, ordermethod), - DeprecationWarning) - except AttributeError: - cmeth = getattr(cls, 'cw_' + ordermethod) - if support_args(cmeth, 'select'): - cmeth(select, attr, var) - else: - warn('[3.14] %s should now take (select, attr, var) and ' - 'modify the syntax tree when desired instead of ' - 'returning something' % cmeth, DeprecationWarning) - orderterm = cmeth(attr, var.name) - if orderterm is not None: - try: - var, order = orderterm.split() - except ValueError: - if '(' in orderterm: - cls.error('ignore %s until %s is upgraded', - orderterm, cmeth) - orderterm = None - elif not ' ' in orderterm.strip(): - var = orderterm - order = 'ASC' - if orderterm is not None: - select.add_sort_var(select.get_variable(var), - order=='ASC') + cmeth = getattr(cls, 'cw_' + ordermethod) + cmeth(select, attr, var) @classmethod @cached @@ -548,12 +502,12 @@ return NotImplemented def __eq__(self, other): - if isinstance(self.eid, integer_types): + if isinstance(self.eid, int): return self.eid == other.eid return self is other def __hash__(self): - if isinstance(self.eid, integer_types): + if isinstance(self.eid, int): return self.eid return super(Entity, self).__hash__() @@ -622,14 +576,6 @@ """ return self.has_eid() and self._cw_is_saved - @deprecated('[3.24] cw_metainformation is deprecated') - @cached - def cw_metainformation(self): - source = self.cw_source[0].name - return {'type': self.cw_etype, - 'extid': self.cwuri if source != 'system' else None, - 'source': {'uri': source}} - def cw_check_perm(self, action): self.e_schema.check_perm(self._cw, action, eid=self.eid) @@ -662,10 +608,8 @@ kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid return self._cw.build_url(method, **kwargs) - def rest_path(self, *args, **kwargs): # XXX cw_rest_path + def rest_path(self): # XXX cw_rest_path """returns a REST-like (relative) path for this entity""" - if args or kwargs: - warn("[3.24] rest_path doesn't take parameters anymore", DeprecationWarning) mainattr, needcheck = self.cw_rest_attr_info() etype = str(self.e_schema) path = etype.lower() @@ -691,7 +635,7 @@ if path is None: # fallback url: / url is used as cw entities uri, # prefer it to //eid/ - return text_type(value) + return str(value) return u'%s/%s' % (path, self._cw.url_quote(value)) def cw_attr_metadata(self, attr, metadata): @@ -709,7 +653,7 @@ attr = str(attr) if value is _marker: value = getattr(self, attr) - if isinstance(value, string_types): + if isinstance(value, str): value = value.strip() if value is None or value == '': # don't use "not", 0 is an acceptable value return u'' @@ -759,11 +703,6 @@ assert self.has_eid() execute = self._cw.execute skip_copy_for = {'subject': set(), 'object': set()} - for rtype in self.skip_copy_for: - skip_copy_for['subject'].add(rtype) - warn('[3.14] skip_copy_for on entity classes (%s) is deprecated, ' - 'use cw_skip_copy_for instead with list of couples (rtype, role)' % self.cw_etype, - DeprecationWarning) for rtype, role in self.cw_skip_copy_for: assert role in ('subject', 'object'), role skip_copy_for[role].add(rtype) @@ -1343,29 +1282,6 @@ for rqlexpr in self.e_schema.get_rqlexprs(action): self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None) - # deprecated stuff ######################################################### - - @deprecated('[3.16] use cw_set() instead of set_attributes()') - def set_attributes(self, **kwargs): # XXX cw_set_attributes - if kwargs: - self.cw_set(**kwargs) - - @deprecated('[3.16] use cw_set() instead of set_relations()') - def set_relations(self, **kwargs): # XXX cw_set_relations - """add relations to the given object. To set a relation where this entity - is the object of the relation, use 'reverse_' as argument name. - - Values may be an entity or eid, a list of entities or eids, or None - (meaning that all relations of the given type from or to this object - should be deleted). - """ - if kwargs: - self.cw_set(**kwargs) - - @deprecated('[3.13] use entity.cw_clear_all_caches()') - def clear_all_caches(self): - return self.cw_clear_all_caches() - # attribute and relation descriptors ########################################## @@ -1381,13 +1297,6 @@ return self return eobj.cw_attr_value(self._attrname) - @deprecated('[3.10] assign to entity.cw_attr_cache[attr] or entity.cw_edited[attr]') - def __set__(self, eobj, value): - if hasattr(eobj, 'cw_edited') and not eobj.cw_edited.saved: - eobj.cw_edited[self._attrname] = value - else: - eobj.cw_attr_cache[self._attrname] = value - class Relation(object): """descriptor that controls schema relation access""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/__init__.py --- a/cubicweb/etwist/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -""" CW - nevow/twisted client - -""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/http.py --- a/cubicweb/etwist/http.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -"""twisted server for CubicWeb web instances - -:organization: Logilab -:copyright: 2001-2011 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" - - - - -class HTTPResponse(object): - """An object representing an HTTP Response to be sent to the client. - """ - def __init__(self, twisted_request, code=None, headers=None, stream=None): - self._headers_out = headers - self._twreq = twisted_request - self._stream = stream - self._code = code - - self._init_headers() - self._finalize() - - def _init_headers(self): - if self._headers_out is None: - return - # initialize headers - for k, values in self._headers_out.getAllRawHeaders(): - self._twreq.responseHeaders.setRawHeaders(k, values) - # add content-length if not present - if (self._headers_out.getHeader('content-length') is None - and self._stream is not None): - self._twreq.setHeader('content-length', len(self._stream)) - - def _finalize(self): - # cw_failed is set on errors such as "connection aborted by client". In - # such cases, req.finish() was already called and calling it a twice - # would crash - if getattr(self._twreq, 'cw_failed', False): - return - # we must set code before writing anything, else it's too late - if self._code is not None: - self._twreq.setResponseCode(self._code) - if self._stream is not None: - self._twreq.write(str(self._stream)) - self._twreq.finish() - - def __repr__(self): - return "<%s.%s code=%d>" % (self.__module__, self.__class__.__name__, self._code) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/request.py --- a/cubicweb/etwist/request.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Twisted request handler for CubicWeb""" - -from six import text_type - -from cubicweb.web.request import CubicWebRequestBase - - -class CubicWebTwistedRequestAdapter(CubicWebRequestBase): - """ from twisted .req to cubicweb .form - req.files are put into .form[] - """ - def __init__(self, req, vreg): - self._twreq = req - super(CubicWebTwistedRequestAdapter, self).__init__( - vreg, req.args, headers=req.received_headers) - for key, name_stream_list in req.files.items(): - for name, stream in name_stream_list: - if name is not None: - name = text_type(name, self.encoding) - self.form.setdefault(key, []).append((name, stream)) - # 3.16.4 backward compat - if len(self.form[key]) == 1: - self.form[key] = self.form[key][0] - self.content = self._twreq.content # stream - - def http_method(self): - """returns 'POST', 'GET', 'HEAD', etc.""" - return self._twreq.method - - def relative_path(self, includeparams=True): - """return the normalized path of the request (ie at least relative to - the instance's root, but some other normalization may be needed so that - the returned path may be used to compare to generated urls - - :param includeparams: - boolean indicating if GET form parameters should be kept in the path - """ - path = self._twreq.uri[1:] # remove the root '/' - if not includeparams: - path = path.split('?', 1)[0] - return path diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/server.py --- a/cubicweb/etwist/server.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""twisted server for CubicWeb web instances""" - -import sys -import traceback -import threading -from cgi import FieldStorage, parse_header -from functools import partial -import warnings - -from cubicweb.statsd_logger import statsd_timeit - -from twisted.internet import reactor, task, threads -from twisted.web import http, server -from twisted.web import resource -from twisted.web.server import NOT_DONE_YET - - -from logilab.mtconverter import xml_escape -from logilab.common.decorators import monkeypatch - -from cubicweb import ConfigurationError, CW_EVENT_MANAGER -from cubicweb.utils import json_dumps -from cubicweb.web import DirectResponse -from cubicweb.web.application import CubicWebPublisher -from cubicweb.etwist.request import CubicWebTwistedRequestAdapter -from cubicweb.etwist.http import HTTPResponse - - -def start_task(interval, func): - lc = task.LoopingCall(func) - # wait until interval has expired to actually start the task, else we have - # to wait all tasks to be finished for the server to be actually started - lc.start(interval, now=False) - - -class CubicWebRootResource(resource.Resource): - def __init__(self, config, repo): - resource.Resource.__init__(self) - self.config = config - # instantiate publisher here and not in init_publisher to get some - # checks done before daemonization (eg versions consistency) - self.appli = CubicWebPublisher(repo, config) - self.base_url = config['base-url'] - global MAX_POST_LENGTH - MAX_POST_LENGTH = config['max-post-length'] - - def init_publisher(self): - config = self.config - # when we have an in-memory repository, clean unused sessions every XX - # seconds and properly shutdown the server - if config['repository-uri'] == 'inmemory://': - if config.mode != 'test': - reactor.addSystemEventTrigger('before', 'shutdown', - self.shutdown_event) - warnings.warn( - 'twisted server does not start repository looping tasks anymore; ' - 'use the standalone "scheduler" command if needed' - ) - self.set_url_rewriter() - CW_EVENT_MANAGER.bind('after-registry-reload', self.set_url_rewriter) - - def start_service(self): - start_task(self.appli.session_handler.clean_sessions_interval, - self.appli.session_handler.clean_sessions) - - def set_url_rewriter(self): - self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter') - - def shutdown_event(self): - """callback fired when the server is shutting down to properly - clean opened sessions - """ - self.appli.repo.shutdown() - - def getChild(self, path, request): - """Indicate which resource to use to process down the URL's path""" - return self - - def on_request_finished_ko(self, request, reason): - # annotate the twisted request so that we're able later to check for - # failure without having to dig into request's internal attributes such - # as _disconnected - request.cw_failed = True - self.warning('request finished abnormally: %s', reason) - - def render(self, request): - """Render a page from the root resource""" - finish_deferred = request.notifyFinish() - finish_deferred.addErrback(partial(self.on_request_finished_ko, request)) - # reload modified files in debug mode - if self.config.debugmode: - self.config.uiprops.reload_if_needed() - self.appli.vreg.reload_if_needed() - if self.config['profile']: # default profiler don't trace threads - return self.render_request(request) - else: - deferred = threads.deferToThread(self.render_request, request) - return NOT_DONE_YET - - @statsd_timeit - def render_request(self, request): - try: - # processing HUGE files (hundred of megabytes) in http.processReceived - # blocks other HTTP requests processing - # due to the clumsy & slow parsing algorithm of cgi.FieldStorage - # so we deferred that part to the cubicweb thread - request.process_multipart() - return self._render_request(request) - except Exception: - trace = traceback.format_exc() - return HTTPResponse(stream='
%s
' % xml_escape(trace), - code=500, twisted_request=request) - - def _render_request(self, request): - origpath = request.path - host = request.host - if self.url_rewriter is not None: - # XXX should occur before authentication? - path = self.url_rewriter.rewrite(host, origpath, request) - request.uri.replace(origpath, path, 1) - req = CubicWebTwistedRequestAdapter(request, self.appli.vreg) - try: - ### Try to generate the actual request content - content = self.appli.handle_request(req) - except DirectResponse as ex: - return ex.response - # at last: create twisted object - return HTTPResponse(code = req.status_out, - headers = req.headers_out, - stream = content, - twisted_request=req._twreq) - - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - @classmethod - def debug(cls, msg, *a, **kw): - pass - info = warning = error = critical = exception = debug - - -JSON_PATHS = set(('json',)) -FRAME_POST_PATHS = set(('validateform',)) - -orig_gotLength = http.Request.gotLength -@monkeypatch(http.Request) -def gotLength(self, length): - orig_gotLength(self, length) - if length > MAX_POST_LENGTH: # length is 0 on GET - path = self.channel._path.split('?', 1)[0].rstrip('/').rsplit('/', 1)[-1] - self.clientproto = 'HTTP/1.1' # not yet initialized - self.channel.persistent = 0 # force connection close on cleanup - self.setResponseCode(http.REQUEST_ENTITY_TOO_LARGE) - if path in JSON_PATHS: # XXX better json path detection - self.setHeader('content-type',"application/json") - body = json_dumps({'reason': 'request max size exceeded'}) - elif path in FRAME_POST_PATHS: # XXX better frame post path detection - self.setHeader('content-type',"text/html") - body = ('' % json_dumps( (False, 'request max size exceeded', None) )) - else: - self.setHeader('content-type',"text/html") - body = ("Processing Failed" - "request max size exceeded") - self.setHeader('content-length', str(len(body))) - self.write(body) - # see request.finish(). Done here since we get error due to not full - # initialized request - self.finished = 1 - if not self.queued: - self._cleanup() - for d in self.notifications: - d.callback(None) - self.notifications = [] - -@monkeypatch(http.Request) -def requestReceived(self, command, path, version): - """Called by channel when all data has been received. - - This method is not intended for users. - """ - self.content.seek(0, 0) - self.args = {} - self.files = {} - self.stack = [] - self.method, self.uri = command, path - self.clientproto = version - x = self.uri.split('?', 1) - if len(x) == 1: - self.path = self.uri - else: - self.path, argstring = x - self.args = http.parse_qs(argstring, 1) - # cache the client and server information, we'll need this later to be - # serialized and sent with the request so CGIs will work remotely - self.client = self.channel.transport.getPeer() - self.host = self.channel.transport.getHost() - # Argument processing - ctype = self.getHeader('content-type') - self._do_process_multipart = False - if self.method == "POST" and ctype: - key, pdict = parse_header(ctype) - if key == 'application/x-www-form-urlencoded': - self.args.update(http.parse_qs(self.content.read(), 1)) - self.content.seek(0) - elif key == 'multipart/form-data': - # defer this as it can be extremely time consumming - # with big files - self._do_process_multipart = True - self.process() - -@monkeypatch(http.Request) -def process_multipart(self): - if not self._do_process_multipart: - return - form = FieldStorage(self.content, self.received_headers, - environ={'REQUEST_METHOD': 'POST'}, - keep_blank_values=1, - strict_parsing=1) - for key in form: - values = form[key] - if not isinstance(values, list): - values = [values] - for value in values: - if value.filename: - if value.done != -1: # -1 is transfer has been interrupted - self.files.setdefault(key, []).append((value.filename, value.file)) - else: - self.files.setdefault(key, []).append((None, None)) - else: - self.args.setdefault(key, []).append(value.value) - -from logging import getLogger -from cubicweb import set_log_methods -LOGGER = getLogger('cubicweb.twisted') -set_log_methods(CubicWebRootResource, LOGGER) - -def run(config, debug=None, repo=None): - # repo may by passed during test. - # - # Test has already created a repo object so we should not create a new one. - # Explicitly passing the repo object avoid relying on the fragile - # config.repository() cache. We could imagine making repo a mandatory - # argument and receives it from the starting command directly. - if debug is not None: - config.debugmode = debug - config.check_writeable_uid_directory(config.appdatahome) - # create the site - if repo is None: - repo = config.repository() - root_resource = CubicWebRootResource(config, repo) - website = server.Site(root_resource) - # serve it via standard HTTP on port set in the configuration - port = config['port'] or 8080 - interface = config['interface'] - reactor.suggestThreadPoolSize(config['webserver-threadpool-size']) - reactor.listenTCP(port, website, interface=interface) - if not config.debugmode: - if sys.platform == 'win32': - raise ConfigurationError("Under windows, you must use the service management " - "commands (e.g : 'net start my_instance)'") - from logilab.common.daemon import daemonize - LOGGER.info('instance started in the background on %s', root_resource.base_url) - whichproc = daemonize(config['pid-file'], umask=config['umask']) - if whichproc: # 1 = orig process, 2 = first fork, None = second fork (eg daemon process) - return whichproc # parent process - root_resource.init_publisher() # before changing uid - if config['uid'] is not None: - from logilab.common.daemon import setugid - setugid(config['uid']) - root_resource.start_service() - LOGGER.info('instance started on %s', root_resource.base_url) - # avoid annoying warnign if not in Main Thread - signals = threading.currentThread().getName() == 'MainThread' - if config['profile']: - import cProfile - cProfile.runctx('reactor.run(installSignalHandlers=%s)' % signals, - globals(), locals(), config['profile']) - else: - reactor.run(installSignalHandlers=signals) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/service.py --- a/cubicweb/etwist/service.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -from __future__ import print_function - -import os -import sys - -try: - import win32serviceutil - import win32service -except ImportError: - print('Win32 extensions for Python are likely not installed.') - sys.exit(3) - -from os.path import join - -from cubicweb.etwist.server import (CubicWebRootResource, reactor, server) - -from logilab.common.shellutils import rm - -import logging -from logging import getLogger, handlers -from cubicweb import set_log_methods -from cubicweb.cwconfig import CubicWebConfiguration as cwcfg - - -def _check_env(env): - env_vars = ('CW_INSTANCES_DIR', 'CW_INSTANCES_DATA_DIR', 'CW_RUNTIME_DIR') - for var in env_vars: - if var not in env: - raise Exception('The environment variables %s must be set.' % - ', '.join(env_vars)) - if not env.get('USERNAME'): - env['USERNAME'] = 'cubicweb' - - -class CWService(object, win32serviceutil.ServiceFramework): - _svc_name_ = None - _svc_display_name_ = None - instance = None - - def __init__(self, *args, **kwargs): - win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs) - cwcfg.load_cwctl_plugins() - logger = getLogger('cubicweb') - set_log_methods(CubicWebRootResource, logger) - - def SvcStop(self): - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - logger = getLogger('cubicweb.twisted') - logger.info('stopping %s service' % self.instance) - reactor.stop() - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - - def SvcDoRun(self): - self.ReportServiceStatus(win32service.SERVICE_START_PENDING) - logger = getLogger('cubicweb.twisted') - handler = handlers.NTEventLogHandler('cubicweb') - handler.setLevel(logging.INFO) - logger.addHandler(handler) - logger.info('starting %s service' % self.instance) - try: - _check_env(os.environ) - # create the site - config = cwcfg.config_for(self.instance) - config.init_log(force=True) - config.debugmode = False - logger.info('starting cubicweb instance %s ', self.instance) - config.info('clear ui caches') - rm(join(config.appdatahome, 'uicache', '*')) - root_resource = CubicWebRootResource(config, config.repository()) - website = server.Site(root_resource) - # serve it via standard HTTP on port set in the configuration - port = config['port'] or 8080 - logger.info('listening on port %s' % port) - reactor.listenTCP(port, website) - root_resource.init_publisher() - root_resource.start_service() - logger.info('instance started on %s', root_resource.base_url) - self.ReportServiceStatus(win32service.SERVICE_RUNNING) - reactor.run() - except Exception as e: - logger.error('service %s stopped (cause: %s)' % (self.instance, e)) - logger.exception('what happened ...') - self.ReportServiceStatus(win32service.SERVICE_STOPPED) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/test/data/views.py --- a/cubicweb/etwist/test/data/views.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""only for unit tests !""" - -from cubicweb.view import View -from cubicweb.predicates import match_http_method - -class PutView(View): - __regid__ = 'put' - __select__ = match_http_method('PUT') | match_http_method('POST') - binary = True - - def call(self): - self.w(self._cw.content.read()) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/test/unittest_server.py --- a/cubicweb/etwist/test/unittest_server.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . - -import os, os.path as osp, glob -import urllib - -from cubicweb.devtools.httptest import CubicWebServerTC - - -class ETwistHTTPTC(CubicWebServerTC): - def test_put_content(self): - data = {'hip': 'hop'} - headers = {'Content-Type': 'application/x-www-form-urlencoded'} - body = urllib.urlencode(data) - response = self.web_request('?vid=put', method='PUT', body=body) - self.assertEqual(body, response.body) - response = self.web_request('?vid=put', method='POST', body=body, - headers=headers) - self.assertEqual(body, response.body) - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/twconfig.py --- a/cubicweb/etwist/twconfig.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""twisted server configurations: - -* the "all-in-one" configuration to get a web instance running in a twisted - web server integrating a repository server in the same process (only available - if the repository part of the software is installed -""" - - -from os.path import join - -from logilab.common.configuration import Method, merge_options - -from cubicweb.cwconfig import CONFIGURATIONS -from cubicweb.server.serverconfig import ServerConfiguration -from cubicweb.web.webconfig import WebConfiguration - - -class WebConfigurationBase(WebConfiguration): - """web instance (in a twisted web server) client of a RQL server""" - - options = merge_options(( - # ctl configuration - ('port', - {'type' : 'int', - 'default': None, - 'help': 'http server port number (default to 8080)', - 'group': 'web', 'level': 0, - }), - ('interface', - {'type' : 'string', - 'default': '0.0.0.0', - 'help': 'http server address on which to listen (default to everywhere)', - 'group': 'web', 'level': 1, - }), - ('max-post-length', - {'type' : 'bytes', - 'default': '100MB', - 'help': 'maximum length of HTTP request. Default to 100 MB.', - 'group': 'web', 'level': 1, - }), - ('profile', - {'type' : 'string', - 'default': None, - 'help': 'profile code and use the specified file to store stats if this option is set', - 'group': 'web', 'level': 3, - }), - ('host', - {'type' : 'string', - 'default': None, - 'help': 'host name if not correctly detectable through gethostname', - 'group': 'main', 'level': 1, - }), - ('pid-file', - {'type' : 'string', - 'default': Method('default_pid_file'), - 'help': 'repository\'s pid file', - 'group': 'main', 'level': 2, - }), - ('uid', - {'type' : 'string', - 'default': None, - 'help': 'unix user, if this option is set, use the specified user to start \ -the repository rather than the user running the command', - 'group': 'main', 'level': WebConfiguration.mode == 'system' - }), - ('webserver-threadpool-size', - {'type': 'int', - 'default': 4, - 'help': "size of twisted's reactor threadpool. It should probably be not too \ -much greater than connection-poolsize", - 'group': 'web', 'level': 3, - }), - ) + WebConfiguration.options) - - def server_file(self): - return join(self.apphome, '%s-%s.py' % (self.appid, self.name)) - - def default_base_url(self): - from socket import getfqdn - return 'http://%s:%s/' % (self['host'] or getfqdn().lower(), self['port'] or 8080) - - -class AllInOneConfiguration(WebConfigurationBase, ServerConfiguration): - """repository and web instance in the same twisted process""" - name = 'all-in-one' - options = merge_options(WebConfigurationBase.options - + ServerConfiguration.options) - - cubicweb_appobject_path = WebConfigurationBase.cubicweb_appobject_path | ServerConfiguration.cubicweb_appobject_path - cube_appobject_path = WebConfigurationBase.cube_appobject_path | ServerConfiguration.cube_appobject_path - - -CONFIGURATIONS.append(AllInOneConfiguration) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/etwist/twctl.py --- a/cubicweb/etwist/twctl.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""cubicweb-clt handlers for twisted""" - -from cubicweb.toolsutils import CommandHandler -from cubicweb.web.webctl import WebCreateHandler, WebUpgradeHandler -from cubicweb.server import serverctl - -# trigger configuration registration -import cubicweb.etwist.twconfig # pylint: disable=W0611 - -class TWCreateHandler(WebCreateHandler): - cfgname = 'twisted' - -class TWStartHandler(CommandHandler): - cmdname = 'start' - cfgname = 'twisted' - - def start_server(self, config): - from cubicweb.etwist import server - return server.run(config) - -class TWStopHandler(CommandHandler): - cmdname = 'stop' - cfgname = 'twisted' - - def poststop(self): - pass - -class TWUpgradeHandler(WebUpgradeHandler): - cfgname = 'twisted' - - -class AllInOneCreateHandler(serverctl.RepositoryCreateHandler, - TWCreateHandler): - """configuration to get an instance running in a twisted web server - integrating a repository server in the same process - """ - cfgname = 'all-in-one' - - def bootstrap(self, cubes, automatic=False, inputlevel=0): - """bootstrap this configuration""" - serverctl.RepositoryCreateHandler.bootstrap(self, cubes, automatic, inputlevel) - TWCreateHandler.bootstrap(self, cubes, automatic, inputlevel) - -class AllInOneStartHandler(TWStartHandler): - cmdname = 'start' - cfgname = 'all-in-one' - subcommand = 'cubicweb-twisted' - -class AllInOneStopHandler(CommandHandler): - cmdname = 'stop' - cfgname = 'all-in-one' - subcommand = 'cubicweb-twisted' - - def poststop(self): - pass - -class AllInOneUpgradeHandler(TWUpgradeHandler): - cfgname = 'all-in-one' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/ext/rest.py --- a/cubicweb/ext/rest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/ext/rest.py Fri May 24 16:29:14 2019 +0200 @@ -38,9 +38,7 @@ from itertools import chain from logging import getLogger from os.path import join - -from six import text_type -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from docutils import statemachine, nodes, utils, io from docutils.core import Publisher @@ -403,7 +401,7 @@ the data formatted as HTML or the original data if an error occurred """ req = context._cw - if isinstance(data, text_type): + if isinstance(data, str): encoding = 'utf-8' # remove unprintable characters unauthorized in xml data = data.translate(ESC_UCAR_TABLE) @@ -448,8 +446,8 @@ return res except BaseException: LOGGER.exception('error while publishing ReST text') - if not isinstance(data, text_type): - data = text_type(data, encoding, 'replace') + if not isinstance(data, str): + data = data.encode(encoding, 'replace') return xml_escape(req._('error while publishing ReST text') + '\n\n' + data) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/ext/test/unittest_rest.py --- a/cubicweb/ext/test/unittest_rest.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/ext/test/unittest_rest.py Fri May 24 16:29:14 2019 +0200 @@ -15,7 +15,6 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from six import PY3 from logilab.common.testlib import unittest_main from cubicweb.devtools.testlib import CubicWebTC @@ -93,8 +92,7 @@ context = self.context(req) out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:toto`') self.assertTrue(out.startswith("

an error occurred while interpreting this " - "rql directive: ObjectNotFound(%s'toto'" % - ('' if PY3 else 'u')), + "rql directive: ObjectNotFound('toto'"), out) def test_rql_role_without_vid(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/integrity.py --- a/cubicweb/hooks/integrity.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/integrity.py Fri May 24 16:29:14 2019 +0200 @@ -23,8 +23,6 @@ from threading import Lock -from six import text_type - from cubicweb import validation_error, neg_role from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES, RQLConstraint, RQLUniqueConstraint) @@ -276,7 +274,7 @@ value = edited[attr] except KeyError: continue # no text to tidy - if isinstance(value, text_type): # filter out None and Binary + if isinstance(value, str): # filter out None and Binary if getattr(entity, str(metaattr)) == 'text/html': edited[attr] = soup2xhtml(value, self._cw.encoding) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/notification.py --- a/cubicweb/hooks/notification.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/notification.py Fri May 24 16:29:14 2019 +0200 @@ -20,7 +20,6 @@ from logilab.common.textutils import normalize_text -from logilab.common.deprecation import deprecated from cubicweb import RegistryNotFound from cubicweb.predicates import is_instance @@ -28,11 +27,6 @@ from cubicweb.sobjects.supervising import SupervisionMailOp -@deprecated('[3.17] use notify_on_commit instead') -def RenderAndSendNotificationView(cnx, view, viewargs=None): - notify_on_commit(cnx, view, viewargs) - - def notify_on_commit(cnx, view, viewargs=None): """register a notification view (see :class:`~cubicweb.sobjects.notification.NotificationView`) to be sent at @@ -76,6 +70,12 @@ # to prevent them all. self.exception('Notification failed') + if self.cnx.vreg.config.mode == "test": + # reraise in testing context because we actually want to + # have those exceptions here and that self.exception is + # filtered in test context + raise + class NotificationHook(hook.Hook): __abstract__ = True diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/syncschema.py --- a/cubicweb/hooks/syncschema.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/syncschema.py Fri May 24 16:29:14 2019 +0200 @@ -33,10 +33,11 @@ from logilab.common.decorators import clear_cache from cubicweb import _ -from cubicweb import validation_error +from cubicweb import validation_error, ETYPE_NAME_MAP from cubicweb.predicates import is_instance from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES, - CONSTRAINTS, UNIQUE_CONSTRAINTS, ETYPE_NAME_MAP) + CONSTRAINTS, UNIQUE_CONSTRAINTS) +from cubicweb.schema import constraint_name_for from cubicweb.server import hook, schemaserial as ss, schema2sql as y2sql from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.hooks.synccomputed import RecomputeAttributeOperation @@ -758,7 +759,7 @@ 'IntervalBoundConstraint', 'StaticVocabularyConstraint'): cnx.system_sql('ALTER TABLE %s%s DROP CONSTRAINT %s' - % (SQL_PREFIX, rdef.subject, self.oldcstr.name_for(rdef))) + % (SQL_PREFIX, rdef.subject, constraint_name_for(self.oldcstr, rdef))) def revertprecommit_event(self): # revert changes on in memory schema @@ -812,7 +813,7 @@ # oldcstr is the new constraint when the attribute is being added in the same # transaction or when constraint value is updated. So we've to take care... if oldcstr is not None: - oldcstrname = self.oldcstr.name_for(rdef) + oldcstrname = constraint_name_for(self.oldcstr, rdef) if oldcstrname != cstrname: cnx.system_sql('ALTER TABLE %s%s DROP CONSTRAINT %s' % (SQL_PREFIX, rdef.subject, oldcstrname)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/test/data-computed/schema.py --- a/cubicweb/hooks/test/data-computed/schema.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/test/data-computed/schema.py Fri May 24 16:29:14 2019 +0200 @@ -17,6 +17,8 @@ # with CubicWeb. If not, see . from yams.buildobjs import EntityType, String, Int, SubjectRelation, RelationDefinition +from cubicweb import _ + THISYEAR = 2014 class Person(EntityType): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/test/unittest_hooks.py --- a/cubicweb/hooks/test/unittest_hooks.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/test/unittest_hooks.py Fri May 24 16:29:14 2019 +0200 @@ -24,8 +24,6 @@ from datetime import datetime -from six import text_type - from pytz import utc from cubicweb import ValidationError @@ -211,7 +209,7 @@ with self.assertRaises(ValidationError) as cm: cnx.execute('INSERT CWUser X: X login "admin", X upassword "admin"') ex = cm.exception - ex.translate(text_type) + ex.translate(str) self.assertIsInstance(ex.entity, int) self.assertEqual(ex.errors, {'': u'some relations violate a unicity constraint', diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/hooks/test/unittest_syncsession.py --- a/cubicweb/hooks/test/unittest_syncsession.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/hooks/test/unittest_syncsession.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ syncschema.py hooks are mostly tested in server/test/unittest_migrations.py """ -from six import text_type - from cubicweb import ValidationError from cubicweb.devtools.testlib import CubicWebTC @@ -35,13 +33,13 @@ with self.assertRaises(ValidationError) as cm: req.execute('INSERT CWProperty X: X pkey "bla.bla", ' 'X value "hop", X for_user U') - cm.exception.translate(text_type) + cm.exception.translate(str) self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'}) with self.assertRaises(ValidationError) as cm: req.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"') - cm.exception.translate(text_type) + cm.exception.translate(str) self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'}) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/i18n.py --- a/cubicweb/i18n.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/i18n.py Fri May 24 16:29:14 2019 +0200 @@ -16,17 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """Some i18n/gettext utilities.""" -from __future__ import print_function - - - import re import os from os.path import join, basename, splitext, exists from glob import glob -from six import PY2 - from cubicweb.toolsutils import create_dir def extract_from_tal(files, output_file): @@ -42,11 +36,7 @@ def add_msg(w, msgid, msgctx=None): """write an empty pot msgid definition""" - if PY2 and isinstance(msgid, unicode): - msgid = msgid.encode('utf-8') if msgctx: - if PY2 and isinstance(msgctx, unicode): - msgctx = msgctx.encode('utf-8') w('msgctxt "%s"\n' % msgctx) msgid = msgid.replace('"', r'\"').splitlines() if len(msgid) > 1: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/i18n/de.po --- a/cubicweb/i18n/de.po Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/i18n/de.po Fri May 24 16:29:14 2019 +0200 @@ -259,12 +259,6 @@ msgid "CWAttribute_plural" msgstr "Attribute" -msgid "CWCache" -msgstr "Cache" - -msgid "CWCache_plural" -msgstr "Caches" - msgid "CWComputedRType" msgstr "" @@ -546,9 +540,6 @@ msgid "New CWAttribute" msgstr "Neue finale Relationsdefinition" -msgid "New CWCache" -msgstr "Neuer Anwendungs-Cache" - msgid "New CWComputedRType" msgstr "" @@ -767,9 +758,6 @@ msgid "This CWAttribute:" msgstr "diese finale Relationsdefinition:" -msgid "This CWCache:" -msgstr "Dieser Anwendungs-Cache:" - msgid "This CWComputedRType:" msgstr "" @@ -974,13 +962,6 @@ msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" msgstr "" -msgid "" -"a simple cache entity characterized by a name and a validity date. The " -"target application is responsible for updating timestamp when necessary to " -"invalidate the cache (typically in hooks). Also, checkout the AppObject." -"get_cache() method." -msgstr "" - msgid "abstract base class for transitions" msgstr "abstrakte Basisklasse für Übergänge" @@ -1107,9 +1088,6 @@ msgid "add a CWAttribute" msgstr "" -msgid "add a CWCache" -msgstr "" - msgid "add a CWComputedRType" msgstr "" @@ -2663,9 +2641,6 @@ msgid "gc" msgstr "" -msgid "generic plot" -msgstr "generischer Plot" - msgid "generic relation to link one entity to another" msgstr "generische Relation zur Verbindung einer Entität mit einer anderen" @@ -3202,10 +3177,6 @@ msgid "name" msgstr "Name" -msgctxt "CWCache" -msgid "name" -msgstr "Name" - msgctxt "CWComputedRType" msgid "name" msgstr "" @@ -3250,9 +3221,6 @@ msgid "name" msgstr "Name" -msgid "name of the cache" -msgstr "Name des Caches" - msgid "" "name of the main variables which should be used in the selection if " "necessary (comma separated)" @@ -3317,9 +3285,6 @@ msgid "no related entity" msgstr "keine verknüpfte Entität" -msgid "no repository sessions found" -msgstr "keine Datenbank-Sitzung gefunden" - msgid "no selected entities" msgstr "keine Entitäten ausgewählt" @@ -3372,9 +3337,6 @@ msgid "open all" msgstr "alle öffnen" -msgid "opened sessions" -msgstr "offene Sitzungen" - msgid "opened web sessions" msgstr "offene Web-Sitzungen" @@ -3782,6 +3744,9 @@ msgid "severity" msgstr "" +msgid "should css be compiled and store in uicache" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3855,6 +3820,9 @@ msgid "specifying %s is mandatory" msgstr "" +msgid "specifying an URL is mandatory" +msgstr "" + msgid "" "start timestamp of the currently in synchronization, or NULL when no " "synchronization in progress." @@ -4045,9 +4013,6 @@ msgid "the prefered email" msgstr "primäre E-Mail-Adresse" -msgid "the system source has its configuration stored on the file-system" -msgstr "" - msgid "there is no next page" msgstr "" @@ -4070,13 +4035,6 @@ msgid "thursday" msgstr "Donnerstag" -msgid "timestamp" -msgstr "Datum" - -msgctxt "CWCache" -msgid "timestamp" -msgstr "gültig seit" - msgid "timetable" msgstr "Zeitplan" @@ -4576,9 +4534,21 @@ #~ msgid "Browse by category" #~ msgstr "nach Kategorien navigieren" +#~ msgid "CWCache" +#~ msgstr "Cache" + +#~ msgid "CWCache_plural" +#~ msgstr "Caches" + +#~ msgid "New CWCache" +#~ msgstr "Neuer Anwendungs-Cache" + #~ msgid "No account? Try public access at %s" #~ msgstr "Kein Konto? Zur öffentlichen Website: %s" +#~ msgid "This CWCache:" +#~ msgstr "Dieser Anwendungs-Cache:" + #~ msgid "anonymous" #~ msgstr "anonym" @@ -4599,9 +4569,25 @@ #~ "Fehler beim Zugriff auf Quelle %s, möglicherweise sind die Daten " #~ "unvollständig." +#~ msgid "generic plot" +#~ msgstr "generischer Plot" + +#~ msgctxt "CWCache" +#~ msgid "name" +#~ msgstr "Name" + +#~ msgid "name of the cache" +#~ msgstr "Name des Caches" + #~ msgid "no edited fields specified for entity %s" #~ msgstr "kein Eingabefeld spezifiziert Für Entität %s" +#~ msgid "no repository sessions found" +#~ msgstr "keine Datenbank-Sitzung gefunden" + +#~ msgid "opened sessions" +#~ msgstr "offene Sitzungen" + #~ msgid "the value \"%s\" is already used, use another one" #~ msgstr "" #~ "Der Wert \"%s\" wird bereits benutzt, bitte verwenden Sie einen anderen " @@ -4609,3 +4595,10 @@ #~ msgid "timeline" #~ msgstr "Zeitleiste" + +#~ msgid "timestamp" +#~ msgstr "Datum" + +#~ msgctxt "CWCache" +#~ msgid "timestamp" +#~ msgstr "gültig seit" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/i18n/en.po --- a/cubicweb/i18n/en.po Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/i18n/en.po Fri May 24 16:29:14 2019 +0200 @@ -248,12 +248,6 @@ msgid "CWAttribute_plural" msgstr "Attributes" -msgid "CWCache" -msgstr "CubicWeb Cache" - -msgid "CWCache_plural" -msgstr "CubicWeb Caches" - msgid "CWComputedRType" msgstr "Virtual relation" @@ -524,9 +518,6 @@ msgid "New CWAttribute" msgstr "New attribute" -msgid "New CWCache" -msgstr "New cache" - msgid "New CWComputedRType" msgstr "New virtual relation" @@ -618,7 +609,8 @@ msgstr "Passwords" msgid "Persistent session. Used by cubicweb.pyramid to store the session data." -msgstr "Persistent session. Used by cubicweb.pyramid to store the session data." +msgstr "" +"Persistent session. Used by cubicweb.pyramid to store the session data." msgid "Please note that this is only a shallow copy" msgstr "" @@ -743,9 +735,6 @@ msgid "This CWAttribute:" msgstr "This attribute:" -msgid "This CWCache:" -msgstr "This cache:" - msgid "This CWComputedRType:" msgstr "This virtual relation:" @@ -936,13 +925,6 @@ msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" msgstr "" -msgid "" -"a simple cache entity characterized by a name and a validity date. The " -"target application is responsible for updating timestamp when necessary to " -"invalidate the cache (typically in hooks). Also, checkout the AppObject." -"get_cache() method." -msgstr "" - msgid "abstract base class for transitions" msgstr "" @@ -1069,9 +1051,6 @@ msgid "add a CWAttribute" msgstr "" -msgid "add a CWCache" -msgstr "" - msgid "add a CWComputedRType" msgstr "" @@ -2612,9 +2591,6 @@ msgid "gc" msgstr "memory leak" -msgid "generic plot" -msgstr "" - msgid "generic relation to link one entity to another" msgstr "" @@ -3122,10 +3098,6 @@ msgid "name" msgstr "name" -msgctxt "CWCache" -msgid "name" -msgstr "name" - msgctxt "CWComputedRType" msgid "name" msgstr "name" @@ -3170,9 +3142,6 @@ msgid "name" msgstr "name" -msgid "name of the cache" -msgstr "" - msgid "" "name of the main variables which should be used in the selection if " "necessary (comma separated)" @@ -3235,9 +3204,6 @@ msgid "no related entity" msgstr "" -msgid "no repository sessions found" -msgstr "" - msgid "no selected entities" msgstr "" @@ -3290,9 +3256,6 @@ msgid "open all" msgstr "" -msgid "opened sessions" -msgstr "" - msgid "opened web sessions" msgstr "" @@ -3696,6 +3659,9 @@ msgid "severity" msgstr "" +msgid "should css be compiled and store in uicache" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3762,6 +3728,9 @@ msgid "specifying %s is mandatory" msgstr "" +msgid "specifying an URL is mandatory" +msgstr "" + msgid "" "start timestamp of the currently in synchronization, or NULL when no " "synchronization in progress." @@ -3948,9 +3917,6 @@ msgid "the prefered email" msgstr "" -msgid "the system source has its configuration stored on the file-system" -msgstr "" - msgid "there is no next page" msgstr "" @@ -3973,13 +3939,6 @@ msgid "thursday" msgstr "" -msgid "timestamp" -msgstr "" - -msgctxt "CWCache" -msgid "timestamp" -msgstr "timestamp" - msgid "timetable" msgstr "" @@ -4459,3 +4418,23 @@ msgid "you should probably delete that property" msgstr "" + +#~ msgid "CWCache" +#~ msgstr "CubicWeb Cache" + +#~ msgid "CWCache_plural" +#~ msgstr "CubicWeb Caches" + +#~ msgid "New CWCache" +#~ msgstr "New cache" + +#~ msgid "This CWCache:" +#~ msgstr "This cache:" + +#~ msgctxt "CWCache" +#~ msgid "name" +#~ msgstr "name" + +#~ msgctxt "CWCache" +#~ msgid "timestamp" +#~ msgstr "timestamp" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/i18n/es.po --- a/cubicweb/i18n/es.po Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/i18n/es.po Fri May 24 16:29:14 2019 +0200 @@ -262,12 +262,6 @@ msgid "CWAttribute_plural" msgstr "Atributos" -msgid "CWCache" -msgstr "Cache" - -msgid "CWCache_plural" -msgstr "Caches" - msgid "CWComputedRType" msgstr "" @@ -550,9 +544,6 @@ msgid "New CWAttribute" msgstr "Nueva definición de relación final" -msgid "New CWCache" -msgstr "Agregar Caché" - msgid "New CWComputedRType" msgstr "" @@ -772,9 +763,6 @@ msgid "This CWAttribute:" msgstr "Esta definición de relación final:" -msgid "This CWCache:" -msgstr "Este Caché:" - msgid "This CWComputedRType:" msgstr "" @@ -983,18 +971,6 @@ msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" msgstr "se espera un número (en segundos) ó 20s, 10min, 24h ó 4d " -msgid "" -"a simple cache entity characterized by a name and a validity date. The " -"target application is responsible for updating timestamp when necessary to " -"invalidate the cache (typically in hooks). Also, checkout the AppObject." -"get_cache() method." -msgstr "" -"un caché simple caracterizado por un nombre y una fecha de validez. Es\n" -"el código de la instancia quién es responsable de actualizar la fecha de\n" -"validez mientras el caché debe ser invalidado (en general en un hook).\n" -"Para recuperar un caché, hace falta utilizar el método\n" -"get_cache(cachename)." - msgid "abstract base class for transitions" msgstr "Clase de base abstracta para la transiciones" @@ -1121,9 +1097,6 @@ msgid "add a CWAttribute" msgstr "" -msgid "add a CWCache" -msgstr "" - msgid "add a CWComputedRType" msgstr "" @@ -2712,9 +2685,6 @@ msgid "gc" msgstr "fuga de memoria" -msgid "generic plot" -msgstr "Gráfica Genérica" - msgid "generic relation to link one entity to another" msgstr "Relación genérica para ligar entidades" @@ -3248,10 +3218,6 @@ msgid "name" msgstr "Nombre" -msgctxt "CWCache" -msgid "name" -msgstr "Nombre" - msgctxt "CWComputedRType" msgid "name" msgstr "" @@ -3296,9 +3262,6 @@ msgid "name" msgstr "Nombre" -msgid "name of the cache" -msgstr "Nombre del Caché" - msgid "" "name of the main variables which should be used in the selection if " "necessary (comma separated)" @@ -3363,9 +3326,6 @@ msgid "no related entity" msgstr "No posee entidad asociada" -msgid "no repository sessions found" -msgstr "Ninguna sesión encontrada" - msgid "no selected entities" msgstr "No hay entidades seleccionadas" @@ -3418,9 +3378,6 @@ msgid "open all" msgstr "Abrir todos" -msgid "opened sessions" -msgstr "Sesiones abiertas" - msgid "opened web sessions" msgstr "Sesiones Web abiertas" @@ -3831,6 +3788,9 @@ msgid "severity" msgstr "severidad" +msgid "should css be compiled and store in uicache" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3904,6 +3864,9 @@ msgid "specifying %s is mandatory" msgstr "especificar %s es obligatorio" +msgid "specifying an URL is mandatory" +msgstr "" + msgid "" "start timestamp of the currently in synchronization, or NULL when no " "synchronization in progress." @@ -4096,10 +4059,6 @@ msgid "the prefered email" msgstr "Dirección principal de email" -msgid "the system source has its configuration stored on the file-system" -msgstr "" -"el sistema fuente tiene su configuración almacenada en el sistema de archivos" - msgid "there is no next page" msgstr "no existe página siguiente" @@ -4122,13 +4081,6 @@ msgid "thursday" msgstr "Jueves" -msgid "timestamp" -msgstr "Fecha" - -msgctxt "CWCache" -msgid "timestamp" -msgstr "Válido desde" - msgid "timetable" msgstr "Tablero de tiempos" @@ -4655,6 +4607,12 @@ #~ msgid "Browse by category" #~ msgstr "Busca por categoría" +#~ msgid "CWCache" +#~ msgstr "Cache" + +#~ msgid "CWCache_plural" +#~ msgstr "Caches" + #~ msgid "CWSourceSchemaConfig" #~ msgstr "Configuraciones de Esquema de Fuente" @@ -4667,18 +4625,36 @@ #~ msgid "Entity and relation supported by this source" #~ msgstr "Entidades y relaciones aceptadas por esta fuente" +#~ msgid "New CWCache" +#~ msgstr "Agregar Caché" + #~ msgid "New CWSourceSchemaConfig" #~ msgstr "Nueva parte de mapeo de fuente" #~ msgid "No account? Try public access at %s" #~ msgstr "No esta registrado? Use el acceso público en %s" +#~ msgid "This CWCache:" +#~ msgstr "Este Caché:" + #~ msgid "This CWSourceSchemaConfig:" #~ msgstr "Esta parte de mapeo de fuente:" #~ msgid "You can't change this relation" #~ msgstr "Usted no puede modificar esta relación" +#~ msgid "" +#~ "a simple cache entity characterized by a name and a validity date. The " +#~ "target application is responsible for updating timestamp when necessary " +#~ "to invalidate the cache (typically in hooks). Also, checkout the " +#~ "AppObject.get_cache() method." +#~ msgstr "" +#~ "un caché simple caracterizado por un nombre y una fecha de validez. Es\n" +#~ "el código de la instancia quién es responsable de actualizar la fecha de\n" +#~ "validez mientras el caché debe ser invalidado (en general en un hook).\n" +#~ "Para recuperar un caché, hace falta utilizar el método\n" +#~ "get_cache(cachename)." + #~ msgid "allowed options depends on the source type" #~ msgstr "las opciones permitidas dependen del tipo de fuente" @@ -4764,14 +4740,30 @@ #~ "Un error ha ocurrido al interrogar %s, es posible que los \n" #~ "datos visibles se encuentren incompletos" +#~ msgid "generic plot" +#~ msgstr "Gráfica Genérica" + #~ msgid "inlined relation %(rtype)s of %(etype)s should be supported" #~ msgstr "" #~ "la relación %(rtype)s del tipo de entidad %(etype)s debe ser aceptada " #~ "('inlined')" +#~ msgctxt "CWCache" +#~ msgid "name" +#~ msgstr "Nombre" + +#~ msgid "name of the cache" +#~ msgstr "Nombre del Caché" + #~ msgid "no edited fields specified for entity %s" #~ msgstr "Ningún campo editable especificado para la entidad %s" +#~ msgid "no repository sessions found" +#~ msgstr "Ninguna sesión encontrada" + +#~ msgid "opened sessions" +#~ msgstr "Sesiones abiertas" + #~ msgctxt "CWSourceSchemaConfig" #~ msgid "options" #~ msgstr "opciones" @@ -4797,6 +4789,11 @@ #~ "la relación %s es aceptada pero ninguna de sus definiciones corresponden " #~ "a los tipos de entidades aceptadas" +#~ msgid "the system source has its configuration stored on the file-system" +#~ msgstr "" +#~ "el sistema fuente tiene su configuración almacenada en el sistema de " +#~ "archivos" + #~ msgid "the value \"%s\" is already used, use another one" #~ msgstr "El valor \"%s\" ya esta en uso, favor de utilizar otro" @@ -4809,6 +4806,13 @@ #~ msgid "timeline" #~ msgstr "Escala de Tiempo" +#~ msgid "timestamp" +#~ msgstr "Fecha" + +#~ msgctxt "CWCache" +#~ msgid "timestamp" +#~ msgstr "Válido desde" + #~ msgid "unknown option(s): %s" #~ msgstr "opcion(es) desconocida(s): %s" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/i18n/fr.po --- a/cubicweb/i18n/fr.po Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/i18n/fr.po Fri May 24 16:29:14 2019 +0200 @@ -202,7 +202,9 @@ msgstr "Permissions des attributs" msgid "Authentication failed. Please check your credentials." -msgstr "Échec de l'authentification. Veuillez vérifier vos identifiant et mot de passe." +msgstr "" +"Échec de l'authentification. Veuillez vérifier vos identifiant et mot de " +"passe." # schema pot file, generated on 2009-09-16 16:46:55 # @@ -256,12 +258,6 @@ msgid "CWAttribute_plural" msgstr "Attributs" -msgid "CWCache" -msgstr "Cache applicatif" - -msgid "CWCache_plural" -msgstr "Caches applicatifs" - msgid "CWComputedRType" msgstr "Relation virtuelle" @@ -546,9 +542,6 @@ msgid "New CWAttribute" msgstr "Nouvelle définition de relation finale" -msgid "New CWCache" -msgstr "Nouveau cache applicatif" - msgid "New CWComputedRType" msgstr "Nouvelle relation virtuelle" @@ -640,7 +633,9 @@ msgstr "Mots de passe" msgid "Persistent session. Used by cubicweb.pyramid to store the session data." -msgstr "Session persistante. Utilisée par cubicweb.pyramid pour stocker les données de session." +msgstr "" +"Session persistante. Utilisée par cubicweb.pyramid pour stocker les données " +"de session." msgid "Please note that this is only a shallow copy" msgstr "Attention, cela n'effectue qu'une copie de surface" @@ -770,9 +765,6 @@ msgid "This CWAttribute:" msgstr "Cette définition de relation finale :" -msgid "This CWCache:" -msgstr "Ce cache applicatif :" - msgid "This CWComputedRType:" msgstr "Cette relation virtuelle :" @@ -981,18 +973,6 @@ msgid "a number (in seconds) or 20s, 10min, 24h or 4d are expected" msgstr "un nombre (en seconde) ou 20s, 10min, 24h ou 4d sont attendus" -msgid "" -"a simple cache entity characterized by a name and a validity date. The " -"target application is responsible for updating timestamp when necessary to " -"invalidate the cache (typically in hooks). Also, checkout the AppObject." -"get_cache() method." -msgstr "" -"un cache simple caractérisé par un nom et une date de validité. C'est\n" -"le code de l'instance qui est responsable de mettre à jour la date de\n" -"validité lorsque le cache doit être invalidé (en général dans un hook).\n" -"Pour récupérer un cache, il faut utiliser utiliser la méthode\n" -"get_cache(cachename)." - msgid "abstract base class for transitions" msgstr "classe de base abstraite pour les transitions" @@ -1119,9 +1099,6 @@ msgid "add a CWAttribute" msgstr "" -msgid "add a CWCache" -msgstr "" - msgid "add a CWComputedRType" msgstr "" @@ -2711,9 +2688,6 @@ msgid "gc" msgstr "fuite mémoire" -msgid "generic plot" -msgstr "tracé de courbes standard" - msgid "generic relation to link one entity to another" msgstr "relation générique pour lier une entité à une autre" @@ -3247,10 +3221,6 @@ msgid "name" msgstr "nom" -msgctxt "CWCache" -msgid "name" -msgstr "nom" - msgctxt "CWComputedRType" msgid "name" msgstr "nom" @@ -3295,9 +3265,6 @@ msgid "name" msgstr "nom" -msgid "name of the cache" -msgstr "nom du cache applicatif" - msgid "" "name of the main variables which should be used in the selection if " "necessary (comma separated)" @@ -3362,9 +3329,6 @@ msgid "no related entity" msgstr "pas d'entité liée" -msgid "no repository sessions found" -msgstr "aucune session trouvée" - msgid "no selected entities" msgstr "pas d'entité sélectionnée" @@ -3417,9 +3381,6 @@ msgid "open all" msgstr "tout ouvrir" -msgid "opened sessions" -msgstr "sessions ouvertes" - msgid "opened web sessions" msgstr "sessions web ouvertes" @@ -3833,6 +3794,9 @@ msgid "severity" msgstr "sévérité" +msgid "should css be compiled and store in uicache" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3906,6 +3870,9 @@ msgid "specifying %s is mandatory" msgstr "spécifier %s est obligatoire" +msgid "specifying an URL is mandatory" +msgstr "" + msgid "" "start timestamp of the currently in synchronization, or NULL when no " "synchronization in progress." @@ -4097,9 +4064,6 @@ msgid "the prefered email" msgstr "l'adresse électronique principale" -msgid "the system source has its configuration stored on the file-system" -msgstr "la source système a sa configuration stockée sur le système de fichier" - msgid "there is no next page" msgstr "Il n'y a pas de page suivante" @@ -4123,13 +4087,6 @@ msgid "thursday" msgstr "jeudi" -msgid "timestamp" -msgstr "date" - -msgctxt "CWCache" -msgid "timestamp" -msgstr "valide depuis" - msgid "timetable" msgstr "emploi du temps" @@ -4628,3 +4585,54 @@ msgid "you should probably delete that property" msgstr "vous devriez probablement supprimer cette propriété" + +#~ msgid "CWCache" +#~ msgstr "Cache applicatif" + +#~ msgid "CWCache_plural" +#~ msgstr "Caches applicatifs" + +#~ msgid "New CWCache" +#~ msgstr "Nouveau cache applicatif" + +#~ msgid "This CWCache:" +#~ msgstr "Ce cache applicatif :" + +#~ msgid "" +#~ "a simple cache entity characterized by a name and a validity date. The " +#~ "target application is responsible for updating timestamp when necessary " +#~ "to invalidate the cache (typically in hooks). Also, checkout the " +#~ "AppObject.get_cache() method." +#~ msgstr "" +#~ "un cache simple caractérisé par un nom et une date de validité. C'est\n" +#~ "le code de l'instance qui est responsable de mettre à jour la date de\n" +#~ "validité lorsque le cache doit être invalidé (en général dans un hook).\n" +#~ "Pour récupérer un cache, il faut utiliser utiliser la méthode\n" +#~ "get_cache(cachename)." + +#~ msgid "generic plot" +#~ msgstr "tracé de courbes standard" + +#~ msgctxt "CWCache" +#~ msgid "name" +#~ msgstr "nom" + +#~ msgid "name of the cache" +#~ msgstr "nom du cache applicatif" + +#~ msgid "no repository sessions found" +#~ msgstr "aucune session trouvée" + +#~ msgid "opened sessions" +#~ msgstr "sessions ouvertes" + +#~ msgid "the system source has its configuration stored on the file-system" +#~ msgstr "" +#~ "la source système a sa configuration stockée sur le système de fichier" + +#~ msgid "timestamp" +#~ msgstr "date" + +#~ msgctxt "CWCache" +#~ msgid "timestamp" +#~ msgstr "valide depuis" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/mail.py --- a/cubicweb/mail.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/mail.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """Common utilies to format / send emails.""" - - from base64 import b64encode, b64decode from time import time from email.mime.multipart import MIMEMultipart @@ -28,21 +26,13 @@ from email.utils import formatdate from socket import gethostname -from six import PY2, PY3, text_type - def header(ustring): - if PY3: - return Header(ustring, 'utf-8') - return Header(ustring.encode('UTF-8'), 'UTF-8') + return Header(ustring, 'utf-8') -def addrheader(uaddr, uname=None): +def addrheader(addr, uname=None): # even if an email address should be ascii, encode it using utf8 since # automatic tests may generate non ascii email address - if PY2: - addr = uaddr.encode('UTF-8') - else: - addr = uaddr if uname: val = '%s <%s>' % (header(uname).encode(), addr) else: @@ -86,7 +76,7 @@ to_addrs and cc_addrs are expected to be a list of email address without name """ - assert isinstance(content, text_type), repr(content) + assert isinstance(content, str), repr(content) msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8') # safety: keep only the first newline try: @@ -97,13 +87,13 @@ if uinfo.get('email'): email = uinfo['email'] elif config and config['sender-addr']: - email = text_type(config['sender-addr']) + email = config['sender-addr'] else: email = u'' if uinfo.get('name'): name = uinfo['name'] elif config and config['sender-name']: - name = text_type(config['sender-name']) + name = config['sender-name'] else: name = u'' msg['From'] = addrheader(email, name) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/md5crypt.py --- a/cubicweb/md5crypt.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/md5crypt.py Fri May 24 16:29:14 2019 +0200 @@ -43,9 +43,6 @@ from hashlib import md5 # pylint: disable=E0611 -from six import text_type, indexbytes -from six.moves import range - def to64 (v, n): ret = bytearray() @@ -56,9 +53,9 @@ return ret def crypt(pw, salt): - if isinstance(pw, text_type): + if isinstance(pw, str): pw = pw.encode('utf-8') - if isinstance(salt, text_type): + if isinstance(salt, str): salt = salt.encode('ascii') # Take care of the magic string if present if salt.startswith(MAGIC): @@ -102,20 +99,20 @@ final = md5(ctx1).digest() # Final xform passwd = b'' - passwd += to64((indexbytes(final, 0) << 16) - |(indexbytes(final, 6) << 8) - |(indexbytes(final, 12)),4) - passwd += to64((indexbytes(final, 1) << 16) - |(indexbytes(final, 7) << 8) - |(indexbytes(final, 13)), 4) - passwd += to64((indexbytes(final, 2) << 16) - |(indexbytes(final, 8) << 8) - |(indexbytes(final, 14)), 4) - passwd += to64((indexbytes(final, 3) << 16) - |(indexbytes(final, 9) << 8) - |(indexbytes(final, 15)), 4) - passwd += to64((indexbytes(final, 4) << 16) - |(indexbytes(final, 10) << 8) - |(indexbytes(final, 5)), 4) - passwd += to64((indexbytes(final, 11)), 2) + passwd += to64((final[0] << 16) + |(final[6] << 8) + |(final[12]),4) + passwd += to64((final[1] << 16) + |(final[7] << 8) + |(final[13]), 4) + passwd += to64((final[2] << 16) + |(final[8] << 8) + |(final[14]), 4) + passwd += to64((final[3] << 16) + |(final[9] << 8) + |(final[15]), 4) + passwd += to64((final[4] << 16) + |(final[10] << 8) + |(final[5]), 4) + passwd += to64((final[11]), 2) return passwd diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/migration.py --- a/cubicweb/migration.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/migration.py Fri May 24 16:29:14 2019 +0200 @@ -16,9 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """utilities for instances migration""" -from __future__ import print_function - - import sys import os @@ -26,16 +23,12 @@ import tempfile from os.path import exists, join, basename, splitext from itertools import chain -from warnings import warn - -from six import string_types from logilab.common import IGNORED_EXTENSIONS from logilab.common.decorators import cached from logilab.common.configuration import REQUIRED, read_old_config from logilab.common.shellutils import ASK from logilab.common.changelog import Version -from logilab.common.deprecation import deprecated from cubicweb import ConfigurationError, ExecutionError from cubicweb.cwconfig import CubicWebConfiguration as cwcfg @@ -269,11 +262,20 @@ banner = """entering the migration python shell just type migration commands or arbitrary python code and type ENTER to execute it type "exit" or Ctrl-D to quit the shell and resume operation""" - interact(banner, local=local_ctx) + + # use ipython if available try: - readline.write_history_file(histfile) - except IOError: - pass + from IPython import start_ipython + print(banner) + start_ipython(argv=[], user_ns=local_ctx) + except ImportError: + interact(banner, local=local_ctx) + + try: + readline.write_history_file(histfile) + except IOError: + pass + # delete instance's confirm attribute to avoid questions del self.confirm self.need_wrap = True @@ -349,13 +351,7 @@ scriptlocals['__name__'] = pyname with open(migrscript, 'rb') as fobj: fcontent = fobj.read() - try: - code = compile(fcontent, migrscript, 'exec') - except SyntaxError: - # try without print_function - code = compile(fcontent, migrscript, 'exec', 0, True) - warn('[3.22] script %r should be updated to work with print_function' - % migrscript, DeprecationWarning) + code = compile(fcontent, migrscript, 'exec') exec(code, scriptlocals) if funcname is not None: try: @@ -406,7 +402,7 @@ """modify the list of used cubes in the in-memory config returns newly inserted cubes, including dependencies """ - if isinstance(cubes, string_types): + if isinstance(cubes, str): cubes = (cubes,) origcubes = self.config.cubes() newcubes = [p for p in self.config.expand_cubes(cubes) @@ -415,10 +411,6 @@ self.config.add_cubes(newcubes) return newcubes - @deprecated('[3.20] use drop_cube() instead of remove_cube()') - def cmd_remove_cube(self, cube, removedeps=False): - return self.cmd_drop_cube(cube, removedeps) - def cmd_drop_cube(self, cube, removedeps=False): if removedeps: toremove = self.config.expand_cubes([cube]) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/3.10.0_Any.py --- a/cubicweb/misc/migration/3.10.0_Any.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/migration/3.10.0_Any.py Fri May 24 16:29:14 2019 +0200 @@ -1,5 +1,3 @@ -from six import text_type - add_entity_type('CWSource') add_relation_definition('CWSource', 'cw_source', 'CWSource') add_entity_type('CWSourceHostConfig') @@ -18,7 +16,7 @@ continue config = u'\n'.join('%s=%s' % (key, value) for key, value in cfg.items() if key != 'adapter' and value is not None) - create_entity('CWSource', name=text_type(uri), type=text_type(cfg['adapter']), + create_entity('CWSource', name=uri, type=cfg['adapter'], config=config) commit() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/3.13.8_Any.py --- a/cubicweb/misc/migration/3.13.8_Any.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/migration/3.13.8_Any.py Fri May 24 16:29:14 2019 +0200 @@ -1,4 +1,3 @@ -change_attribute_type('CWCache', 'timestamp', 'TZDatetime') change_attribute_type('CWUser', 'last_login_time', 'TZDatetime') change_attribute_type('CWSource', 'latest_retrieval', 'TZDatetime') drop_attribute('CWSource', 'synchronizing') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/3.15.0_Any.py --- a/cubicweb/misc/migration/3.15.0_Any.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/migration/3.15.0_Any.py Fri May 24 16:29:14 2019 +0200 @@ -16,7 +16,7 @@ sconfig.set_option(opt, val) except OptionError: continue - cfgstr = text_type(generate_source_config(sconfig), source._cw.encoding) + cfgstr = str(generate_source_config(sconfig), source._cw.encoding) source.cw_set(config=cfgstr) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/3.27.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/misc/migration/3.27.0_Any.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,4 @@ +option_removed('host') +option_removed('uid') +option_removed('webserver-threadpool-size') +drop_entity_type('CWCache') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/bootstrapmigration_repository.py --- a/cubicweb/misc/migration/bootstrapmigration_repository.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/migration/bootstrapmigration_repository.py Fri May 24 16:29:14 2019 +0200 @@ -19,11 +19,6 @@ it should only include low level schema changes """ - -from __future__ import print_function - -from six import text_type - from cubicweb import ConfigurationError from cubicweb.server.session import hooks_control from cubicweb.server import schemaserial as ss @@ -120,7 +115,6 @@ default = yams.DATE_FACTORY_MAP[atype](default) else: assert atype == 'String', atype - default = text_type(default) return Binary.zpickle(default) dbh = repo.system_source.dbhelper diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/migration/postcreate.py --- a/cubicweb/misc/migration/postcreate.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/migration/postcreate.py Fri May 24 16:29:14 2019 +0200 @@ -16,19 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """cubicweb post creation script, set user's workflow""" -from __future__ import print_function - -from six import text_type - from cubicweb import _ # insert versions create_entity('CWProperty', pkey=u'system.version.cubicweb', - value=text_type(config.cubicweb_version())) + value=str(config.cubicweb_version())) for cube in config.cubes(): create_entity('CWProperty', pkey=u'system.version.%s' % cube.lower(), - value=text_type(config.cube_version(cube))) + value=str(config.cube_version(cube))) # some entities have been added before schema entities, add their missing 'is' and # 'is_instance_of' relations @@ -56,7 +52,7 @@ print('Hopefully this is not a production instance...') elif anonlogin: from cubicweb.server import create_user - create_user(session, text_type(anonlogin), anonpwd, u'guests') + create_user(session, anonlogin, anonpwd, u'guests') # need this since we already have at least one user in the database (the default admin) for user in rql('Any X WHERE X is CWUser').entities(): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/scripts/migration_helper.py --- a/cubicweb/misc/scripts/migration_helper.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/misc/scripts/migration_helper.py Fri May 24 16:29:14 2019 +0200 @@ -19,9 +19,6 @@ """Helper functions for migrations that aren't reliable enough or too dangerous to be available in the standard migration environment """ -from __future__ import print_function - - def drop_entity_types_fast(*etypes, **kwargs): """drop an entity type bypassing all hooks diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/misc/scripts/repair_file_1-9_migration.py --- a/cubicweb/misc/scripts/repair_file_1-9_migration.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -"""execute this script if you've migration to file >= 1.9.0 with cubicweb <= 3.9.2 - -FYI, this migration occurred : -* on our intranet on July 07 2010 -* on our extranet on July 16 2010 -""" -from __future__ import print_function - -try: - backupinstance, = __args__ -except ValueError: - print('USAGE: cubicweb-ctl shell repair_file_1-9_migration.py -- ') - print() - print('you should restored the backup on a new instance, accessible through pyro') - -from cubicweb import cwconfig, dbapi -from cubicweb.server.session import hooks_control - -defaultadmin = repo.config.default_admin_config -backupcfg = cwconfig.instance_configuration(backupinstance) -backupcfg.repairing = True -backuprepo, backupcnx = dbapi.in_memory_repo_cnx(backupcfg, defaultadmin['login'], - password=defaultadmin['password'], - host='localhost') -backupcu = backupcnx.cursor() - -with hooks_control(session, session.HOOKS_DENY_ALL): - rql('SET X is Y WHERE X is File, Y name "File", NOT X is Y') - rql('SET X is_instance_of Y WHERE X is File, Y name "File", NOT X is_instance_of Y') - for rtype, in backupcu.execute('DISTINCT Any RTN WHERE X relation_type RT, RT name RTN,' - 'X from_entity Y, Y name "Image", X is CWRelation, ' - 'EXISTS(XX is CWRelation, XX relation_type RT, ' - 'XX from_entity YY, YY name "File")'): - if rtype in ('is', 'is_instance_of'): - continue - print(rtype) - for feid, xeid in backupcu.execute('Any F,X WHERE F %s X, F is IN (File,Image)' % rtype): - print('restoring relation %s between file %s and %s' % (rtype, feid, xeid), end=' ') - print(rql('SET F %s X WHERE F eid %%(f)s, X eid %%(x)s, NOT F %s X' % (rtype, rtype), - {'f': feid, 'x': xeid})) - - for rtype, in backupcu.execute('DISTINCT Any RTN WHERE X relation_type RT, RT name RTN,' - 'X to_entity Y, Y name "Image", X is CWRelation, ' - 'EXISTS(XX is CWRelation, XX relation_type RT, ' - 'XX to_entity YY, YY name "File")'): - print(rtype) - for feid, xeid in backupcu.execute('Any F,X WHERE X %s F, F is IN (File,Image)' % rtype): - print('restoring relation %s between %s and file %s' % (rtype, xeid, feid), end=' ') - print(rql('SET X %s F WHERE F eid %%(f)s, X eid %%(x)s, NOT X %s F' % (rtype, rtype), - {'f': feid, 'x': xeid})) - -commit() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/multipart.py --- a/cubicweb/multipart.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/multipart.py Fri May 24 16:29:14 2019 +0200 @@ -37,16 +37,12 @@ __version__ = '0.1' __license__ = 'MIT' +from io import BytesIO from tempfile import TemporaryFile +from urllib.parse import parse_qs from wsgiref.headers import Headers import re, sys -try: - from io import BytesIO -except ImportError: # pragma: no cover (fallback for Python 2.5) - from StringIO import StringIO as BytesIO -from six import PY3, text_type -from six.moves.urllib.parse import parse_qs ############################################################################## ################################ Helper & Misc ################################ @@ -54,7 +50,7 @@ # Some of these were copied from bottle: http://bottle.paws.de/ try: - from collections import MutableMapping as DictMixin + from collections.abc import MutableMapping as DictMixin except ImportError: # pragma: no cover (fallback for Python 2.5) from UserDict import DictMixin @@ -88,7 +84,7 @@ yield key, value def tob(data, enc='utf8'): # Convert strings to bytes (py2 and py3) - return data.encode(enc) if isinstance(data, text_type) else data + return data.encode(enc) if isinstance(data, str) else data def copy_file(stream, target, maxread=-1, buffer_size=2*16): ''' Read from :stream and write to :target until :maxread or EOF. ''' @@ -105,10 +101,10 @@ ############################################################################## _special = re.escape('()<>@,;:\\"/[]?={} \t') -_re_special = re.compile('[%s]' % _special) +_re_special = re.compile(r'[%s]' % _special) _qstr = '"(?:\\\\.|[^"])*"' # Quoted string _value = '(?:[^%s]+|%s)' % (_special, _qstr) # Save or quoted string -_option = '(?:;|^)\s*([^%s]+)\s*=\s*(%s)' % (_special, _value) +_option = r'(?:;|^)\s*([^%s]+)\s*=\s*(%s)' % (_special, _value) _re_option = re.compile(_option) # key=value part of an Content-Type like header def header_quote(val): @@ -400,15 +396,11 @@ data = stream.read(mem_limit) if stream.read(1): # These is more that does not fit mem_limit raise MultipartError("Request too big. Increase MAXMEM.") - if PY3: - data = data.decode('ascii') + data = data.decode('ascii') data = parse_qs(data, keep_blank_values=True) for key, values in data.items(): for value in values: - if PY3: - forms[key] = value - else: - forms[key.decode(charset)] = value.decode(charset) + forms[key] = value else: raise MultipartError("Unsupported content type.") except MultipartError: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/predicates.py --- a/cubicweb/predicates.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/predicates.py Fri May 24 16:29:14 2019 +0200 @@ -24,11 +24,7 @@ from warnings import warn from operator import eq -from six import string_types, integer_types -from six.moves import range - -from logilab.common.deprecation import deprecated -from logilab.common.registry import Predicate, objectify_predicate, yes +from logilab.common.registry import Predicate, objectify_predicate from yams.schema import BASE_TYPES, role_name from rql.nodes import Function @@ -38,8 +34,6 @@ from cubicweb.uilib import eid_param from cubicweb.schema import split_expression -yes = deprecated('[3.15] import yes() from use logilab.common.registry')(yes) - # abstract predicates / mixin helpers ########################################### @@ -85,12 +79,7 @@ - `accept_none` is False and some cell in the column has a None value (this may occurs with outer join) """ - def __init__(self, once_is_enough=None, accept_none=True, mode='all'): - if once_is_enough is not None: - warn("[3.14] once_is_enough is deprecated, use mode='any'", - DeprecationWarning, stacklevel=2) - if once_is_enough: - mode = 'any' + def __init__(self, accept_none=True, mode='all'): assert mode in ('any', 'all'), 'bad mode %s' % mode self.once_is_enough = mode == 'any' self.accept_none = accept_none @@ -618,7 +607,7 @@ super(is_instance, self).__init__(**kwargs) self.expected_etypes = expected_etypes for etype in self.expected_etypes: - assert isinstance(etype, string_types), etype + assert isinstance(etype, str), etype def __str__(self): return '%s(%s)' % (self.__class__.__name__, @@ -672,13 +661,13 @@ See :class:`~cubicweb.predicates.EntityPredicate` documentation for entity lookup / score rules according to the input context. """ - def __init__(self, scorefunc, once_is_enough=None, mode='all'): - super(score_entity, self).__init__(mode=mode, once_is_enough=once_is_enough) + def __init__(self, scorefunc, mode='all'): + super(score_entity, self).__init__(mode=mode) def intscore(*args, **kwargs): score = scorefunc(*args, **kwargs) if not score: return 0 - if isinstance(score, integer_types): + if isinstance(score, int): return score return 1 self.score_entity = intscore @@ -690,8 +679,8 @@ You can give 'image/' to match any image for instance, or 'image/png' to match only PNG images. """ - def __init__(self, mimetype, once_is_enough=None, mode='all'): - super(has_mimetype, self).__init__(mode=mode, once_is_enough=once_is_enough) + def __init__(self, mimetype, mode='all'): + super(has_mimetype, self).__init__(mode=mode) self.mimetype = mimetype def score_entity(self, entity): @@ -995,8 +984,8 @@ See :class:`~cubicweb.predicates.EntityPredicate` documentation for entity lookup / score rules according to the input context. """ - def __init__(self, expression, once_is_enough=None, mode='all', user_condition=False): - super(rql_condition, self).__init__(mode=mode, once_is_enough=once_is_enough) + def __init__(self, expression, mode='all', user_condition=False): + super(rql_condition, self).__init__(mode=mode) self.user_condition = user_condition if user_condition: rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression @@ -1084,7 +1073,7 @@ ','.join(str(s) for s in self.expected)) -def on_fire_transition(etype, tr_names, from_state_name=None): +def on_fire_transition(etype, tr_names): """Return 1 when entity of the type `etype` is going through transition of a name included in `tr_names`. @@ -1096,9 +1085,7 @@ See :class:`cubicweb.entities.wfobjs.TrInfo` for more information. """ - if from_state_name is not None: - warn("on_fire_transition's from_state_name argument is unused", DeprecationWarning) - if isinstance(tr_names, string_types): + if isinstance(tr_names, str): tr_names = set((tr_names,)) def match_etype_and_transition(trinfo): # take care trinfo.transition is None when calling change_state @@ -1298,7 +1285,7 @@ raise ValueError("match_form_params() can't be called with both " "positional and named arguments") if expected: - if len(expected) == 1 and not isinstance(expected[0], string_types): + if len(expected) == 1 and not isinstance(expected[0], str): raise ValueError("match_form_params() positional arguments " "must be strings") super(match_form_params, self).__init__(*expected) @@ -1391,8 +1378,8 @@ is_instance('Version') & (match_transition('ready') | attribute_edited('publication_date')) """ - def __init__(self, attribute, once_is_enough=None, mode='all'): - super(attribute_edited, self).__init__(mode=mode, once_is_enough=once_is_enough) + def __init__(self, attribute, mode='all'): + super(attribute_edited, self).__init__(mode=mode) self._attribute = attribute def score_entity(self, entity): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/__init__.py --- a/cubicweb/pyramid/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -21,10 +21,10 @@ """Pyramid interface to CubicWeb""" import atexit +from configparser import ConfigParser import os import warnings -from six.moves.configparser import SafeConfigParser import wsgicors from cubicweb.cwconfig import CubicWebConfiguration as cwcfg @@ -72,7 +72,7 @@ for fname in settings_filenames: if os.path.exists(fname): - cp = SafeConfigParser() + cp = ConfigParser() cp.read(fname) settings.update(cp.items('main')) break diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/config.py --- a/cubicweb/pyramid/config.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/config.py Fri May 24 16:29:14 2019 +0200 @@ -26,7 +26,7 @@ from cubicweb.cwconfig import CONFIGURATIONS from cubicweb.server.serverconfig import ServerConfiguration from cubicweb.toolsutils import fill_templated_file -from cubicweb.web.webconfig import BaseWebConfiguration +from cubicweb.web.webconfig import BaseWebConfiguration, WebConfigurationBase def get_random_secret_key(): @@ -69,3 +69,29 @@ CONFIGURATIONS.append(CubicWebPyramidConfiguration) + + +class AllInOneConfiguration(WebConfigurationBase, ServerConfiguration): + """repository and web instance in the same Pyramid process""" + name = 'all-in-one' + options = merge_options(( + ('profile', + {'type': 'string', + 'default': None, + 'help': 'profile code and use the specified file to store stats if this option is set', + 'group': 'web', 'level': 3, + }), + ) + WebConfigurationBase.options + ServerConfiguration.options + ) + + cubicweb_appobject_path = ( + WebConfigurationBase.cubicweb_appobject_path + | ServerConfiguration.cubicweb_appobject_path + ) + cube_appobject_path = ( + WebConfigurationBase.cube_appobject_path + | ServerConfiguration.cube_appobject_path + ) + + +CONFIGURATIONS.append(AllInOneConfiguration) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/core.py --- a/cubicweb/pyramid/core.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/core.py Fri May 24 16:29:14 2019 +0200 @@ -23,7 +23,6 @@ import itertools from contextlib import contextmanager -from warnings import warn from cgi import FieldStorage import rql @@ -124,11 +123,6 @@ assert 300 <= ex.status < 400 raise httpexceptions.status_map[ex.status]( ex.location, headers=cw_headers(request)) - except cubicweb.web.StatusResponse as ex: - warn('[3.16] StatusResponse is deprecated use req.status_out', - DeprecationWarning, stacklevel=2) - request.body = ex.content - request.status_int = ex.status except cubicweb.web.Unauthorized: raise httpexceptions.HTTPForbidden( request.cw_request._( @@ -177,15 +171,6 @@ val = (val.filename, val.file) if param == '_cwmsgid': self.set_message_id(val) - elif param == '__message': - warn('[3.13] __message in request parameter is deprecated ' - '(may only be given to .build_url). Seeing this message ' - 'usualy means your application hold some

where ' - 'you should replace use of __message hidden input by ' - 'form.set_message, so new _cwmsgid mechanism is properly ' - 'used', - DeprecationWarning) - self.set_message(val) else: self.form[param] = val @@ -413,12 +398,7 @@ cwcfg = config.registry['cubicweb.config'] for cube in cwcfg.cubes(): - try: - pkgname = 'cubicweb_{}'.format(cube) - mod = __import__(pkgname) - except ImportError: - pkgname = 'cubes.{}'.format(cube) - mod = __import__(pkgname) - mod = getattr(mod, cube) + pkgname = 'cubicweb_{}'.format(cube) + mod = __import__(pkgname) if hasattr(mod, 'includeme'): config.include(pkgname) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/profile.py --- a/cubicweb/pyramid/profile.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/profile.py Fri May 24 16:29:14 2019 +0200 @@ -21,7 +21,6 @@ """ Tools for profiling. See :ref:`profiling`.""" -from __future__ import print_function import cProfile import itertools diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/pyramid.ini.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/pyramid/pyramid.ini.tmpl Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,7 @@ +[main] + +cubicweb.session.secret = %(secret_1)s +cubicweb.auth.authtkt.session.secret = %(secret_2)s +cubicweb.auth.authtkt.persistent.secret = %(secret_3)s +cubicweb.auth.authtkt.session.secure = no +cubicweb.auth.authtkt.persistent.secure = no diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/pyramidctl.py --- a/cubicweb/pyramid/pyramidctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/pyramidctl.py Fri May 24 16:29:14 2019 +0200 @@ -25,8 +25,6 @@ the pyramid script 'pserve'. """ -from __future__ import print_function - import atexit import errno import os @@ -41,8 +39,10 @@ from cubicweb.cwconfig import CubicWebConfiguration as cwcfg from cubicweb.cwctl import CWCTL, InstanceCommand, init_cmdline_log_threshold from cubicweb.pyramid import wsgi_application_from_cwconfig +from cubicweb.pyramid.config import get_random_secret_key from cubicweb.server import serverctl, set_debug from cubicweb.web.webctl import WebCreateHandler +from cubicweb.toolsutils import fill_templated_file import waitress @@ -52,6 +52,17 @@ LOG_LEVELS = ('debug', 'info', 'warning', 'error') +def _generate_pyramid_ini_file(pyramid_ini_path): + """Write a 'pyramid.ini' file into apphome.""" + template_fpath = os.path.join(os.path.dirname(__file__), 'pyramid.ini.tmpl') + context = { + 'secret_1': get_random_secret_key(), + 'secret_2': get_random_secret_key(), + 'secret_3': get_random_secret_key(), + } + fill_templated_file(template_fpath, pyramid_ini_path, context) + + class PyramidCreateHandler(serverctl.RepositoryCreateHandler, WebCreateHandler): cfgname = 'pyramid' @@ -63,6 +74,20 @@ self.config.write_development_ini(cubes) +class AllInOneCreateHandler(serverctl.RepositoryCreateHandler, + WebCreateHandler): + """configuration to get an instance running in a Pyramid web server + integrating a repository server in the same process + """ + cfgname = 'all-in-one' + + def bootstrap(self, cubes, automatic=False, inputlevel=0): + """bootstrap this configuration""" + serverctl.RepositoryCreateHandler.bootstrap(self, cubes, automatic, inputlevel) + WebCreateHandler.bootstrap(self, cubes, automatic, inputlevel) + _generate_pyramid_ini_file(os.path.join(self.config.apphome, "pyramid.ini")) + + class PyramidStartHandler(InstanceCommand): """Start an interactive pyramid server. @@ -320,6 +345,10 @@ filelist_path = os.path.join(cwconfig.apphome, '.pyramid-reload-files.list') + pyramid_ini_path = os.path.join(cwconfig.apphome, "pyramid.ini") + if not os.path.exists(pyramid_ini_path): + _generate_pyramid_ini_file(pyramid_ini_path) + if autoreload and not os.environ.get(self._reloader_environ_key): return self.restart_with_reloader(filelist_path) @@ -358,7 +387,8 @@ 'anymore; use the standalone "scheduler" command if needed' ) try: - waitress.serve(app, host=host, port=port, url_scheme=url_scheme) + waitress.serve(app, host=host, port=port, url_scheme=url_scheme, + clear_untrusted_proxy_headers=True) finally: repo.shutdown() if self._needreload: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/resources.py --- a/cubicweb/pyramid/resources.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/resources.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ """Pyramid resource definitions for CubicWeb.""" -from six import text_type - from rql import TypeResolverException from pyramid.decorator import reify @@ -62,7 +60,7 @@ # conflicting eid/type raise HTTPNotFound() else: - rset = req.execute(st.as_string(), {'x': text_type(self.value)}) + rset = req.execute(st.as_string(), {'x': self.value}) return rset diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/session.py --- a/cubicweb/pyramid/session.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/session.py Fri May 24 16:29:14 2019 +0200 @@ -84,12 +84,11 @@ redis) when using redis as backend. """ -import warnings import logging from contextlib import contextmanager from pyramid.compat import pickle -from pyramid.session import SignedCookieSessionFactory +from pyramid.session import SignedCookieSessionFactory, JSONSerializer, PickleSerializer from cubicweb import ( Binary, @@ -129,6 +128,24 @@ yield cnx +class JSONSerializerWithPickleFallback(object): + def __init__(self): + self.json = JSONSerializer() + self.pickle = PickleSerializer() + + def dumps(self, value): + # maybe catch serialization errors here and keep using pickle + # while finding spots in your app that are not storing + # JSON-serializable objects, falling back to pickle + return self.json.dumps(value) + + def loads(self, value): + try: + return self.json.loads(value) + except ValueError: + return self.pickle.loads(value) + + def CWSessionFactory( secret, cookie_name='session', @@ -177,7 +194,7 @@ reissue_time=reissue_time, hashalg=hashalg, salt=salt, - serializer=serializer) + serializer=serializer if serializer else JSONSerializerWithPickleFallback()) class CWSession(SignedCookieSession): def __init__(self, request): @@ -261,23 +278,6 @@ See also :ref:`defaults_module` """ - settings = config.registry.settings - try: - secret = settings['cubicweb.session.secret'] - except KeyError: - secret = 'notsosecret' - if config.registry['cubicweb.config'].mode != 'test': - warnings.warn(''' - - !! WARNING !! !! WARNING !! - - The session cookies are signed with a static secret key. - To put your own secret key, edit your pyramid.ini file - and set the 'cubicweb.session.secret' key. - - YOU SHOULD STOP THIS INSTANCE unless your really know what you - are doing !! - - ''') + secret = config.registry.settings['cubicweb.session.secret'] session_factory = CWSessionFactory(secret) config.set_session_factory(session_factory) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/test/__init__.py --- a/cubicweb/pyramid/test/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/test/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -15,7 +15,10 @@ def setUp(self): # Skip CubicWebTestTC setUp super(CubicWebTestTC, self).setUp() - settings = {'cubicweb.bwcompat': False} + settings = { + 'cubicweb.bwcompat': False, + 'cubicweb.session.secret': 'test', + } settings.update(self.settings) config = Configurator(settings=settings) config.registry['cubicweb.repository'] = self.repo diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/test/test_config.py --- a/cubicweb/pyramid/test/test_config.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/test/test_config.py Fri May 24 16:29:14 2019 +0200 @@ -19,13 +19,13 @@ import os from os import path +from tempfile import TemporaryDirectory from unittest import TestCase +from unittest.mock import patch -from mock import patch - -from cubicweb.devtools.testlib import TemporaryDirectory from cubicweb.pyramid import config +from cubicweb.pyramid.pyramidctl import _generate_pyramid_ini_file class PyramidConfigTC(TestCase): @@ -36,6 +36,27 @@ self.assertEqual(patched_choice.call_count, 50) self.assertEqual(secret, '0' * 50) + def test_write_pyramid_ini(self): + with TemporaryDirectory() as instancedir: + pyramid_ini_path = path.join(instancedir, "pyramid.ini") + with patch('random.SystemRandom.choice', return_value='0') as patched_choice: + _generate_pyramid_ini_file(pyramid_ini_path) + with open(path.join(instancedir, 'pyramid.ini')) as f: + lines = f.readlines() + + self.assertEqual(patched_choice.call_count, 50 * 3) + + secret = '0' * 50 + + for option in ('cubicweb.session.secret', + 'cubicweb.auth.authtkt.persistent.secret', + 'cubicweb.auth.authtkt.session.secret'): + self.assertIn('{} = {}\n'.format(option, secret), lines) + + for option in ('cubicweb.auth.authtkt.persistent.secure', + 'cubicweb.auth.authtkt.session.secure'): + self.assertIn('{} = {}\n'.format(option, "no"), lines) + def test_write_development_ini(self): with TemporaryDirectory() as instancedir: appid = 'pyramid-instance' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/pyramid/test/test_hooks.py --- a/cubicweb/pyramid/test/test_hooks.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/pyramid/test/test_hooks.py Fri May 24 16:29:14 2019 +0200 @@ -1,7 +1,4 @@ -from six import text_type - from cubicweb.pyramid.test import PyramidCWTest -from cubicweb.pyramid import tools def set_language(request): @@ -11,10 +8,10 @@ cnx.execute('DELETE CWProperty X WHERE X for_user U, U eid %(u)s', {'u': cnx.user.eid}) else: - cnx.user.set_property(u'ui.language', text_type(lang)) + cnx.user.set_property(u'ui.language', lang) cnx.commit() - request.response.text = text_type(cnx.user.properties.get('ui.language', '')) + request.response.text = cnx.user.properties.get('ui.language', '') return request.response @@ -29,7 +26,7 @@ {'u': cnx.user.eid}) cnx.commit() - request.response.text = text_type(','.join(sorted(cnx.user.groups))) + request.response.text = ','.join(sorted(cnx.user.groups)) return request.response diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/repoapi.py --- a/cubicweb/repoapi.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/repoapi.py Fri May 24 16:29:14 2019 +0200 @@ -17,27 +17,17 @@ # with CubicWeb. If not, see . """Official API to access the content of a repository.""" -from warnings import warn - -from six import add_metaclass - -from logilab.common.deprecation import class_deprecated - from cubicweb import AuthenticationError from cubicweb.server.session import Connection -def get_repository(uri=None, config=None, vreg=None): +def get_repository(config, vreg=None): """get a repository for the given URI or config/vregistry (in case we're loading the repository for a client, eg web server, configuration). The returned repository may be an in-memory repository or a proxy object using a specific RPC method, depending on the given URI. """ - if uri is not None: - warn('[3.22] get_repository only wants a config') - - assert config is not None, 'get_repository(config=config)' return config.repository(vreg) @@ -63,8 +53,3 @@ anon_login, anon_password = anoninfo # use vreg's repository cache return connect(repo, anon_login, password=anon_password) - - -@add_metaclass(class_deprecated) -class ClientConnection(Connection): - __deprecation_warning__ = '[3.20] %(cls)s is deprecated, use Connection instead' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/req.py --- a/cubicweb/req.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/req.py Fri May 24 16:29:14 2019 +0200 @@ -17,22 +17,17 @@ # with CubicWeb. If not, see . """Base class for request/session""" -from warnings import warn from datetime import time, datetime, timedelta - -from six import PY2, PY3, text_type -from six.moves.urllib.parse import (parse_qs, parse_qsl, - quote as urlquote, unquote as urlunquote, - urlsplit, urlunsplit) +from urllib.parse import (parse_qs, parse_qsl, + quote as urlquote, unquote as urlunquote, + urlsplit, urlunsplit) from logilab.common.decorators import cached -from logilab.common.deprecation import deprecated from logilab.common.date import ustrftime, strptime, todate, todatetime from rql.utils import rqlvar_maker -from cubicweb import (Unauthorized, NoSelectableObject, NoResultError, - MultipleResultsError, uilib) +from cubicweb import Unauthorized, NoSelectableObject, uilib from cubicweb.rset import ResultSet ONESECOND = timedelta(0, 1, 0) @@ -76,7 +71,7 @@ self.user = None self.lang = None self.local_perm_cache = {} - self._ = text_type + self._ = str def _set_user(self, orig_user): """set the user for this req_session_base @@ -100,10 +95,10 @@ gettext, pgettext = self.vreg.config.translations[lang] except KeyError: assert self.vreg.config.mode == 'test' - gettext = text_type + gettext = str def pgettext(x, y): - return text_type(y) + return str(y) # use _cw.__ to translate a message without registering it to the catalog self._ = self.__ = gettext @@ -182,29 +177,6 @@ cls = self.vreg['etypes'].etype_class(etype) return cls.cw_instantiate(self.execute, **kwargs) - @deprecated('[3.18] use find(etype, **kwargs).entities()') - def find_entities(self, etype, **kwargs): - """find entities of the given type and attribute values. - - >>> users = find_entities('CWGroup', name=u'users') - >>> groups = find_entities('CWGroup') - """ - return self.find(etype, **kwargs).entities() - - @deprecated('[3.18] use find(etype, **kwargs).one()') - def find_one_entity(self, etype, **kwargs): - """find one entity of the given type and attribute values. - raise :exc:`FindEntityError` if can not return one and only one entity. - - >>> users = find_one_entity('CWGroup', name=u'users') - >>> groups = find_one_entity('CWGroup') - Exception() - """ - try: - return self.find(etype, **kwargs).one() - except (NoResultError, MultipleResultsError) as e: - raise FindEntityError("%s: (%s, %s)" % (str(e), etype, kwargs)) - def find(self, etype, **kwargs): """find entities of the given type and attribute values. @@ -248,33 +220,6 @@ if first in ('insert', 'set', 'delete'): raise Unauthorized(self._('only select queries are authorized')) - def get_cache(self, cachename): - """cachename should be dotted names as in : - - - cubicweb.mycache - - cubes.blog.mycache - - etc. - """ - warn.warning('[3.19] .get_cache will disappear soon. ' - 'Distributed caching mechanisms are being introduced instead.' - 'Other caching mechanism can be used more reliably ' - 'to the same effect.', - DeprecationWarning) - if cachename in CACHE_REGISTRY: - cache = CACHE_REGISTRY[cachename] - else: - cache = CACHE_REGISTRY[cachename] = Cache() - _now = datetime.now() - if _now > cache.latest_cache_lookup + ONESECOND: - ecache = self.execute( - 'Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', - {'name': cachename}).get_entity(0, 0) - cache.latest_cache_lookup = _now - if not ecache.valid(cache.cache_creation_date): - cache.clear() - cache.cache_creation_date = _now - return cache - # url generation methods ################################################## def build_url(self, *args, **kwargs): @@ -296,9 +241,6 @@ # not try to process it and directly call req.build_url() base_url = kwargs.pop('base_url', None) if base_url is None: - if kwargs.pop('__secure__', None) is not None: - warn('[3.25] __secure__ argument is deprecated', - DeprecationWarning, stacklevel=2) base_url = self.base_url() path = self.build_url_path(method, kwargs) if not kwargs: @@ -330,9 +272,6 @@ necessary encoding / decoding. Also it's designed to quote each part of a url path and so the '/' character will be encoded as well. """ - if PY2 and isinstance(value, text_type): - quoted = urlquote(value.encode(self.encoding), safe=safe) - return text_type(quoted, self.encoding) return urlquote(str(value), safe=safe) def url_unquote(self, quoted): @@ -341,28 +280,13 @@ decoding is based on `self.encoding` which is the encoding used in `url_quote` """ - if PY3: - return urlunquote(quoted) - if isinstance(quoted, text_type): - quoted = quoted.encode(self.encoding) - try: - return text_type(urlunquote(quoted), self.encoding) - except UnicodeDecodeError: # might occurs on manually typed URLs - return text_type(urlunquote(quoted), 'iso-8859-1') + return urlunquote(quoted) def url_parse_qsl(self, querystring): """return a list of (key, val) found in the url quoted query string""" - if PY3: - for key, val in parse_qsl(querystring): - yield key, val - return - if isinstance(querystring, text_type): - querystring = querystring.encode(self.encoding) for key, val in parse_qsl(querystring): - try: - yield text_type(key, self.encoding), text_type(val, self.encoding) - except UnicodeDecodeError: # might occurs on manually typed URLs - yield text_type(key, 'iso-8859-1'), text_type(val, 'iso-8859-1') + yield key, val + return def rebuild_url(self, url, **newparams): """return the given url with newparams inserted. If any new params @@ -370,8 +294,6 @@ newparams may only be mono-valued. """ - if PY2 and isinstance(url, text_type): - url = url.encode(self.encoding) schema, netloc, path, query, fragment = urlsplit(url) query = parse_qs(query) # sort for testing predictability @@ -442,7 +364,7 @@ as_string = formatters[attrtype] except KeyError: self.error('given bad attrtype %s', attrtype) - return text_type(value) + return str(value) return as_string(value, self, props, displaytime) def format_date(self, date, date_format=None, time=False): @@ -505,12 +427,7 @@ raise ValueError(self._('can\'t parse %(value)r (expected %(format)s)') % {'value': value, 'format': format}) - def base_url(self, **kwargs): + def base_url(self): """Return the root url of the instance.""" - secure = kwargs.pop('secure', None) - if secure is not None: - warn('[3.25] secure argument is deprecated', DeprecationWarning, stacklevel=2) - if kwargs: - raise TypeError('base_url got unexpected keyword arguments %s' % ', '.join(kwargs)) url = self.vreg.config['base-url'] return url if url is None else url.rstrip('/') + '/' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/rqlrewrite.py --- a/cubicweb/rqlrewrite.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/rqlrewrite.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ This is used for instance for read security checking in the repository. """ -from six import text_type, string_types - from rql import nodes as n, stmts, TypeResolverException from rql.utils import common_parent @@ -640,7 +638,7 @@ while argname in self.kwargs: argname = subselect.allocate_varname() subselect.add_constant_restriction(subselect.get_variable(self.u_varname), - 'eid', text_type(argname), 'Substitute') + 'eid', argname, 'Substitute') self.kwargs[argname] = self.session.user.eid add_types_restriction(self.schema, subselect, subselect, solutions=self.solutions) @@ -795,7 +793,7 @@ # insert "U eid %(u)s" stmt.add_constant_restriction( stmt.get_variable(self.u_varname), - 'eid', text_type(argname), 'Substitute') + 'eid', argname, 'Substitute') self.kwargs[argname] = self.session.user.eid return self.u_varname key = (self.current_expr, self.varmap, vname) @@ -917,7 +915,7 @@ return n.Constant(vi['const'], 'Int') return n.VariableRef(stmt.get_variable(selectvar)) vname_or_term = self._get_varname_or_term(node.name) - if isinstance(vname_or_term, string_types): + if isinstance(vname_or_term, str): return n.VariableRef(stmt.get_variable(vname_or_term)) # shared term return vname_or_term.copy(stmt) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/rset.py --- a/cubicweb/rset.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/rset.py Fri May 24 16:29:14 2019 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -17,22 +17,12 @@ # with CubicWeb. If not, see . """The `ResultSet` class which is returned as result of an rql query""" - -from warnings import warn - -from six import PY3, text_type -from six.moves import range - -from logilab.common import nullobject from logilab.common.decorators import cached, clear_cache, copy_cache from rql import nodes, stmts from cubicweb import NotAnEntity, NoResultError, MultipleResultsError, UnknownEid -_MARKER = nullobject() - - class ResultSet(object): """A result set wraps a RQL query result. This object implements partially the list protocol to allow direct use as a list of @@ -52,10 +42,7 @@ :param rql: the original RQL query string """ - def __init__(self, results, rql, args=None, description=None, rqlst=None): - if rqlst is not None: - warn('[3.20] rqlst parameter is deprecated', - DeprecationWarning, stacklevel=2) + def __init__(self, results, rql, args=None, description=None): self.rows = results self.rowcount = results and len(results) or 0 # original query and arguments @@ -371,25 +358,11 @@ rset.limited = (limit, offset) return rset - def printable_rql(self, encoded=_MARKER): + def printable_rql(self): """return the result set's origin rql as a string, with arguments substitued """ - if encoded is not _MARKER: - warn('[3.21] the "encoded" argument is deprecated', DeprecationWarning) - encoding = self.req.encoding - rqlstr = self.syntax_tree().as_string(kwargs=self.args) - if PY3: - return rqlstr - # sounds like we get encoded or unicode string due to a bug in as_string - if not encoded: - if isinstance(rqlstr, text_type): - return rqlstr - return text_type(rqlstr, encoding) - else: - if isinstance(rqlstr, text_type): - return rqlstr.encode(encoding) - return rqlstr + return self.syntax_tree().as_string(kwargs=self.args) # client helper methods ################################################### @@ -401,6 +374,8 @@ if self.rows[i][col] is not None: yield self.get_entity(i, col) + all = entities + def iter_rows_with_entities(self): """ iterates over rows, and for each row eids are converted to plain entities @@ -467,6 +442,34 @@ else: raise MultipleResultsError("Multiple rows were found for one()") + def first(self, col=0): + """Retrieve the first entity from the query. + + If the result set is empty, raises :exc:`NoResultError`. + + :type col: int + :param col: The column localising the entity in the unique row + + :return: the partially initialized `Entity` instance + """ + if len(self) == 0: + raise NoResultError("No row was found for first()") + return self.get_entity(0, col) + + def last(self, col=0): + """Retrieve the last entity from the query. + + If the result set is empty, raises :exc:`NoResultError`. + + :type col: int + :param col: The column localising the entity in the unique row + + :return: the partially initialized `Entity` instance + """ + if len(self) == 0: + raise NoResultError("No row was found for last()") + return self.get_entity(-1, col) + def _make_entity(self, row, col): """Instantiate an entity, and store it in the entity cache""" # build entity instance diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/rtags.py --- a/cubicweb/rtags.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/rtags.py Fri May 24 16:29:14 2019 +0200 @@ -39,8 +39,6 @@ import logging -from six import string_types - from logilab.common.logging_ext import set_log_methods from logilab.common.registry import RegistrableInstance, yes @@ -182,7 +180,7 @@ return tag def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs): - if isinstance(attr, string_types): + if isinstance(attr, str): attr, role = attr, 'subject' else: attr, role = attr diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/schema.py --- a/cubicweb/schema.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/schema.py Fri May 24 16:29:14 2019 +0200 @@ -17,21 +17,14 @@ # with CubicWeb. If not, see . """classes to define schemas for CubicWeb""" -from __future__ import print_function - from functools import wraps import re from os.path import join from hashlib import md5 from logging import getLogger -from warnings import warn - -from six import PY2, text_type, string_types, add_metaclass -from six.moves import range from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty from logilab.common.logging_ext import set_log_methods -from logilab.common.deprecation import deprecated from logilab.common.textutils import splitstrip from logilab.common.graph import get_cycles @@ -45,12 +38,12 @@ cleanup_sys_modules, fill_schema_from_namespace) from yams.buildobjs import _add_relation as yams_add_relation -from rql import parse, nodes, stmts, RQLSyntaxError, TypeResolverException +from rql import parse, nodes, RQLSyntaxError, TypeResolverException from rql.analyze import ETypeResolver import cubicweb from cubicweb import server -from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _ +from cubicweb import ValidationError, Unauthorized, _ PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',)) @@ -96,7 +89,7 @@ 'WorkflowTransition', 'BaseTransition', 'SubWorkflowExitPoint')) -INTERNAL_TYPES = set(('CWProperty', 'CWCache', 'ExternalUri', 'CWDataImport', +INTERNAL_TYPES = set(('CWProperty', 'ExternalUri', 'CWDataImport', 'CWSource', 'CWSourceHostConfig', 'CWSession')) UNIQUE_CONSTRAINTS = ('SizeConstraint', 'FormatConstraint', @@ -147,8 +140,6 @@ added/removed for instance) """ union = parse(u'Any 1 WHERE %s' % rqlstring).as_string() - if PY2 and isinstance(union, str): - union = union.decode('utf-8') return union.split(' WHERE ', 1)[1] @@ -220,7 +211,7 @@ """ self.eid = eid # eid of the entity representing this rql expression assert mainvars, 'bad mainvars %s' % mainvars - if isinstance(mainvars, string_types): + if isinstance(mainvars, str): mainvars = set(splitstrip(mainvars)) elif not isinstance(mainvars, set): mainvars = set(mainvars) @@ -233,8 +224,8 @@ raise RQLSyntaxError(expression) for mainvar in mainvars: if len(self.snippet_rqlst.defined_vars[mainvar].references()) < 2: - _LOGGER.warn('You did not use the %s variable in your RQL ' - 'expression %s', mainvar, self) + _LOGGER.warning('You did not use the %s variable in your RQL ' + 'expression %s', mainvar, self) # graph of links between variables, used by rql rewriter self.vargraph = vargraph(self.snippet_rqlst) # useful for some instrumentation, e.g. localperms permcheck command @@ -570,15 +561,6 @@ return eschemas -def bw_normalize_etype(etype): - if etype in ETYPE_NAME_MAP: - msg = '%s has been renamed to %s, please update your code' % ( - etype, ETYPE_NAME_MAP[etype]) - warn(msg, DeprecationWarning, stacklevel=4) - etype = ETYPE_NAME_MAP[etype] - return etype - - def display_name(req, key, form='', context=None): """return a internationalized string for the key (schema entity or relation name) in a given form @@ -590,9 +572,9 @@ key = key + '_' + form # ensure unicode if context is not None: - return text_type(req.pgettext(context, key)) + return req.pgettext(context, key) else: - return text_type(req._(key)) + return req._(key) def _override_method(cls, method_name=None, pass_original=False): @@ -638,7 +620,7 @@ """ assert action in self.ACTIONS, action try: - return frozenset(g for g in self.permissions[action] if isinstance(g, string_types)) + return frozenset(g for g in self.permissions[action] if isinstance(g, str)) except KeyError: return () @@ -657,7 +639,7 @@ """ assert action in self.ACTIONS, action try: - return tuple(g for g in self.permissions[action] if not isinstance(g, string_types)) + return tuple(g for g in self.permissions[action] if not isinstance(g, str)) except KeyError: return () @@ -993,10 +975,6 @@ return False return True - @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)') - def cardinality(self, subjtype, objtype, target): - return self.rdef(subjtype, objtype).role_cardinality(target) - class CubicWebSchema(Schema): """set of entities and relations schema defining the possible data sets @@ -1038,7 +1016,6 @@ def add_entity_type(self, edef): edef.name = str(edef.name) - edef.name = bw_normalize_etype(edef.name) if not re.match(self.etype_name_re, edef.name): raise BadSchemaDefinition( '%r is not a valid name for an entity type. It should start ' @@ -1084,8 +1061,6 @@ :param: the newly created or just completed relation schema """ rdef.name = rdef.name.lower() - rdef.subject = bw_normalize_etype(rdef.subject) - rdef.object = bw_normalize_etype(rdef.object) rdefs = super(CubicWebSchema, self).add_relation_def(rdef) if rdefs: try: @@ -1182,14 +1157,13 @@ # additional cw specific constraints ########################################### -@monkeypatch(BaseConstraint) -def name_for(self, rdef): +def constraint_name_for(constraint, rdef): """Return a unique, size controlled, name for this constraint applied to given `rdef`. This name may be used as name for the constraint in the database. """ - return 'cstr' + md5((rdef.subject.type + rdef.rtype.type + self.type() - + (self.serialize() or '')).encode('ascii')).hexdigest() + return 'cstr' + md5((rdef.subject.type + rdef.rtype.type + constraint.type() + + (constraint.serialize() or '')).encode('ascii')).hexdigest() class BaseRQLConstraint(RRQLExpression, BaseConstraint): @@ -1352,8 +1326,7 @@ return cls -@add_metaclass(workflowable_definition) -class WorkflowableEntityType(ybo.EntityType): +class WorkflowableEntityType(ybo.EntityType, metaclass=workflowable_definition): """Use this base class instead of :class:`EntityType` to have workflow relations (i.e. `in_state`, `wf_info_for` and `custom_workflow`) on your entity type. @@ -1463,27 +1436,3 @@ if hasperm: return self.regular_formats + tuple(NEED_PERM_FORMATS) return self.regular_formats - - -# XXX itou for some Statement methods - -@_override_method(stmts.ScopeNode, pass_original=True) -def get_etype(self, name, _orig): - return _orig(self, bw_normalize_etype(name)) - - -@_override_method(stmts.Delete, method_name='add_main_variable', - pass_original=True) -def _add_main_variable_delete(self, etype, vref, _orig): - return _orig(self, bw_normalize_etype(etype), vref) - - -@_override_method(stmts.Insert, method_name='add_main_variable', - pass_original=True) -def _add_main_variable_insert(self, etype, vref, _orig): - return _orig(self, bw_normalize_etype(etype), vref) - - -@_override_method(stmts.Select, pass_original=True) -def set_statement_type(self, etype, _orig): - return _orig(self, bw_normalize_etype(etype)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/schemas/__init__.py --- a/cubicweb/schemas/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/schemas/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -32,20 +32,3 @@ # permissions for relation type that should only set by hooks using unsafe # execute, readable by anyone HOOKS_RTYPE_PERMS = RO_REL_PERMS # XXX deprecates - - -from logilab.common.modutils import LazyObject -from logilab.common.deprecation import deprecated -class MyLazyObject(LazyObject): - - def _getobj(self): - try: - return super(MyLazyObject, self)._getobj() - except ImportError: - raise ImportError('In cubicweb 3.14, function %s has been moved to ' - 'cube localperms. Install it first.' % self.obj) - -for name in ('xperm', 'xexpr', 'xrexpr', 'xorexpr', 'sexpr', 'restricted_sexpr', - 'restricted_oexpr', 'oexpr', 'relxperm', 'relxexpr', '_perm'): - msg = '[3.14] import %s from cubes.localperms' % name - globals()[name] = deprecated(msg, name=name, doc='deprecated')(MyLazyObject('cubes.localperms', name)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/schemas/base.py --- a/cubicweb/schemas/base.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/schemas/base.py Fri May 24 16:29:14 2019 +0200 @@ -209,29 +209,6 @@ object = 'ExternalUri' -class CWCache(EntityType): - """a simple cache entity characterized by a name and - a validity date. - - The target application is responsible for updating timestamp - when necessary to invalidate the cache (typically in hooks). - - Also, checkout the AppObject.get_cache() method. - """ - # XXX only handle by hooks, shouldn't be readable/editable at all through - # the ui and so no permissions should be granted, no? - __permissions__ = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers',), - 'update': ('managers', 'users',), # XXX - 'delete': ('managers',), - } - - name = String(required=True, unique=True, maxsize=128, - description=_('name of the cache')) - timestamp = TZDatetime(default='NOW') - - class CWSource(EntityType): __permissions__ = { 'read': ('managers', 'users', 'guests'), diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/selectors.py --- a/cubicweb/selectors.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . - -from warnings import warn - -from six import string_types - -from logilab.common.deprecation import deprecated, class_renamed - -from cubicweb.predicates import * - - -warn('[3.15] cubicweb.selectors renamed into cubicweb.predicates', - DeprecationWarning, stacklevel=2) - -# XXX pre 3.15 bw compat -from cubicweb.appobject import (objectify_selector, traced_selection, - lltrace, yes) - -ExpectedValueSelector = class_renamed('ExpectedValueSelector', - ExpectedValuePredicate) -EClassSelector = class_renamed('EClassSelector', EClassPredicate) -EntitySelector = class_renamed('EntitySelector', EntityPredicate) - - -class on_transition(is_in_state): - """Return 1 if entity is in one of the transitions given as argument list - - Especially useful to match passed transition to enable notifications when - your workflow allows several transition to the same states. - - Note that if workflow `change_state` adapter method is used, this predicate - will not be triggered. - - You should use this instead of your own :class:`score_entity` predicate to - avoid some gotchas: - - * possible views gives a fake entity with no state - * you must use the latest tr info thru the workflow adapter for repository - side checking of the current state - - In debug mode, this predicate can raise: - :raises: :exc:`ValueError` for unknown transition names - (etype workflow only not checked in custom workflow) - - :rtype: int - """ - @deprecated('[3.12] on_transition is deprecated, you should rather use ' - 'on_fire_transition(etype, trname)') - def __init__(self, *expected): - super(on_transition, self).__init__(*expected) - - def _score(self, adapted): - trinfo = adapted.latest_trinfo() - if trinfo and trinfo.by_transition: - return trinfo.by_transition[0].name in self.expected - - def _validate(self, adapted): - wf = adapted.current_workflow - valid = [n.name for n in wf.reverse_transition_of] - unknown = sorted(self.expected.difference(valid)) - if unknown: - raise ValueError("%s: unknown transition(s): %s" - % (wf.name, ",".join(unknown))) - - -entity_implements = class_renamed('entity_implements', is_instance) - -class _but_etype(EntityPredicate): - """accept if the given entity types are not found in the result set. - - See `EntityPredicate` documentation for behaviour when row is not specified. - - :param *etypes: entity types (`string_types`) which should be refused - """ - def __init__(self, *etypes): - super(_but_etype, self).__init__() - self.but_etypes = etypes - - def score(self, req, rset, row, col): - if rset.description[row][col] in self.but_etypes: - return 0 - return 1 - -but_etype = class_renamed('but_etype', _but_etype, 'use ~is_instance(*etypes) instead') - -# XXX deprecated the one_* variants of predicates below w/ multi_xxx(nb=1)? -# take care at the implementation though (looking for the 'row' argument's -# value) -two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset) -two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset) -two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/__init__.py --- a/cubicweb/server/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -20,13 +20,8 @@ The server module contains functions to initialize a new repository. """ -from __future__ import print_function - from contextlib import contextmanager -from six import text_type, string_types -from six.moves import filter - from logilab.common.modutils import LazyObject from logilab.common.textutils import splitstrip from logilab.common.registry import yes @@ -133,7 +128,7 @@ if not debugmode: DEBUG = 0 return - if isinstance(debugmode, string_types): + if isinstance(debugmode, str): for mode in splitstrip(debugmode, sep='|'): DEBUG |= globals()[mode] else: @@ -192,7 +187,7 @@ user = session.create_entity('CWUser', login=login, upassword=pwd) for group in groups: session.execute('SET U in_group G WHERE U eid %(u)s, G name %(group)s', - {'u': user.eid, 'group': text_type(group)}) + {'u': user.eid, 'group': group}) return user @@ -270,17 +265,17 @@ # insert base groups and default admin print('-> inserting default user and default groups.') try: - login = text_type(sourcescfg['admin']['login']) + login = sourcescfg['admin']['login'] pwd = sourcescfg['admin']['password'] except KeyError: if interactive: msg = 'enter login and password of the initial manager account' login, pwd = manager_userpasswd(msg=msg, confirm=True) else: - login, pwd = text_type(source['db-user']), source['db-password'] + login, pwd = source['db-user'], source['db-password'] # sort for eid predicatability as expected in some server tests for group in sorted(BASE_GROUPS): - cnx.create_entity('CWGroup', name=text_type(group)) + cnx.create_entity('CWGroup', name=group) admin = create_user(cnx, login, pwd, u'managers') cnx.execute('SET X owned_by U WHERE X is IN (CWGroup,CWSource), U eid %(u)s', {'u': admin.eid}) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/checkintegrity.py --- a/cubicweb/server/checkintegrity.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/checkintegrity.py Fri May 24 16:29:14 2019 +0200 @@ -20,7 +20,6 @@ * integrity of a CubicWeb repository. Hum actually only the system database is checked. """ -from __future__ import print_function import sys from datetime import datetime diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/hook.py --- a/cubicweb/server/hook.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/hook.py Fri May 24 16:29:14 2019 +0200 @@ -75,7 +75,7 @@ Hooks are being fired immediately on data operations, and it is sometime necessary to delay the actual work down to a time where we can expect all -information to be there, or when all other hooks have run (though take case +information to be there, or when all other hooks have run (though take care since operations may themselves trigger hooks). Also while the order of execution of hooks is data dependant (and thus hard to predict), it is possible to force an order on operations. @@ -245,13 +245,11 @@ .. autoclass:: cubicweb.server.hook.LateOperation .. autoclass:: cubicweb.server.hook.DataOperationMixIn """ -from __future__ import print_function from logging import getLogger from itertools import chain from logilab.common.decorators import classproperty, cached -from logilab.common.deprecation import deprecated, class_renamed from logilab.common.logging_ext import set_log_methods from logilab.common.registry import (NotPredicate, OrPredicate, objectify_predicate) @@ -428,10 +426,6 @@ def issued_from_user_query(cls, req, **kwargs): return 0 if req.hooks_in_progress else 1 -from_dbapi_query = class_renamed('from_dbapi_query', - issued_from_user_query, - message='[3.21] ') - class rechain(object): def __init__(self, *iterators): @@ -445,7 +439,7 @@ named parameters `frometypes` and `toetypes` can be used to restrict target subject and/or object entity types of the relation. - :param \*expected: possible relation types + :param *expected: possible relation types :param frometypes: candidate entity types as subject of relation :param toetypes: candidate entity types as object of relation """ @@ -738,11 +732,6 @@ self.processed = None # 'precommit', 'commit' self.failed = False - @property - @deprecated('[3.19] Operation.session is deprecated, use Operation.cnx instead') - def session(self): - return self.cnx - def register(self, cnx): cnx.add_operation(self, self.insert_index()) @@ -795,7 +784,7 @@ class DataOperationMixIn(object): """Mix-in class to ease applying a single operation on a set of data, - avoiding to create as many as operation as they are individual modification. + avoiding creating as many operations as there are individual modifications. The body of the operation must then iterate over the values that have been stored in a single operation instance. diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/migractions.py --- a/cubicweb/server/migractions.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/migractions.py Fri May 24 16:29:14 2019 +0200 @@ -26,9 +26,6 @@ * add an entity * execute raw RQL queries """ -from __future__ import print_function - - import sys import os @@ -41,18 +38,15 @@ from copy import copy from contextlib import contextmanager -from six import PY2, text_type - -from logilab.common.deprecation import deprecated from logilab.common.decorators import cached, clear_cache from yams.buildobjs import EntityType from yams.constraints import SizeConstraint from yams.schema import RelationDefinitionSchema -from cubicweb import CW_SOFTWARE_ROOT, AuthenticationError, ExecutionError +from cubicweb import CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, AuthenticationError, ExecutionError from cubicweb.predicates import is_instance -from cubicweb.schema import (ETYPE_NAME_MAP, META_RTYPES, VIRTUAL_RTYPES, +from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES, PURE_VIRTUAL_RTYPES, CubicWebRelationSchema, order_eschemas) from cubicweb.cwvreg import CW_EVENT_MANAGER @@ -154,7 +148,7 @@ def cube_upgraded(self, cube, version): self.cmd_set_property('system.version.%s' % cube.lower(), - text_type(version)) + str(version)) self.commit() def shutdown(self): @@ -1006,7 +1000,7 @@ # elif simply renaming an entity type else: self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s', - {'newname': text_type(newname), 'on': oldname}, + {'newname': newname, 'on': oldname}, ask_confirm=False) if commit: self.commit() @@ -1218,8 +1212,6 @@ values = [] for k, v in kwargs.items(): values.append('X %s %%(%s)s' % (k, k)) - if PY2 and isinstance(v, str): - kwargs[k] = unicode(v) rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction)) self.rqlexec(rql, kwargs, ask_confirm=self.verbosity >= 2) if commit: @@ -1251,7 +1243,7 @@ self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,' 'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",' 'S name "%s", R name "%s"' % (etype, rtype), - {'v': text_type(SizeConstraint(size).serialize())}, + {'v': SizeConstraint(size).serialize()}, ask_confirm=self.verbosity >= 2) else: self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,' @@ -1288,7 +1280,7 @@ :rtype: `Workflow` """ - wf = self.cmd_create_entity('Workflow', name=text_type(name), + wf = self.cmd_create_entity('Workflow', name=name, **kwargs) if not isinstance(wfof, (list, tuple)): wfof = (wfof,) @@ -1298,19 +1290,18 @@ for etype in wfof: eschema = self.repo.schema[etype] - etype = text_type(etype) if ensure_workflowable: assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype) assert 'custom_workflow' in eschema.subjrels, _missing_wf_rel(etype) assert 'wf_info_for' in eschema.objrels, _missing_wf_rel(etype) rset = self.rqlexec( 'SET X workflow_of ET WHERE X eid %(x)s, ET name %(et)s', - {'x': wf.eid, 'et': text_type(etype)}, ask_confirm=False) + {'x': wf.eid, 'et': etype}, ask_confirm=False) assert rset, 'unexistant entity type %s' % etype if default: self.rqlexec( 'SET ET default_workflow X WHERE X eid %(x)s, ET name %(et)s', - {'x': wf.eid, 'et': text_type(etype)}, ask_confirm=False) + {'x': wf.eid, 'et': etype}, ask_confirm=False) if commit: self.commit() return wf @@ -1341,13 +1332,13 @@ To set a user specific property value, use appropriate method on CWUser instance. """ - value = text_type(value) + value = str(value) try: prop = self.rqlexec( 'CWProperty X WHERE X pkey %(k)s, NOT X for_user U', - {'k': text_type(pkey)}, ask_confirm=False).get_entity(0, 0) + {'k': str(pkey)}, ask_confirm=False).get_entity(0, 0) except Exception: - self.cmd_create_entity('CWProperty', pkey=text_type(pkey), value=value) + self.cmd_create_entity('CWProperty', pkey=str(pkey), value=value) else: prop.cw_set(value=value) @@ -1385,20 +1376,6 @@ """find entities of the given type and attribute values""" return self.cnx.find(etype, **kwargs) - @deprecated("[3.19] use find(*args, **kwargs).entities() instead") - def cmd_find_entities(self, etype, **kwargs): - """find entities of the given type and attribute values""" - return self.cnx.find(etype, **kwargs).entities() - - @deprecated("[3.19] use find(*args, **kwargs).one() instead") - def cmd_find_one_entity(self, etype, **kwargs): - """find one entity of the given type and attribute values. - - raise :exc:`cubicweb.req.FindEntityError` if can not return one and only - one entity. - """ - return self.cnx.find(etype, **kwargs).one() - def cmd_update_etype_fti_weight(self, etype, weight): if self.repo.system_source.dbdriver == 'postgres': self.sqlexec('UPDATE appears SET weight=%(weight)s ' @@ -1548,10 +1525,6 @@ if commit: self.commit() - @deprecated("[3.15] use rename_relation_type(oldname, newname)") - def cmd_rename_relation(self, oldname, newname, commit=True): - self.cmd_rename_relation_type(oldname, newname, commit) - class ForRqlIterator: """specific rql iterator to make the loop skipable""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/querier.py --- a/cubicweb/server/querier.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/querier.py Fri May 24 16:29:14 2019 +0200 @@ -18,13 +18,8 @@ """Helper classes to execute RQL queries on a set of sources, performing security checking and data aggregation. """ -from __future__ import print_function - from itertools import repeat -from six import text_type, string_types, integer_types -from six.moves import range, zip - from rql import RQLSyntaxError, CoercionError from rql.stmts import Union from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not @@ -45,9 +40,9 @@ ETYPE_PYOBJ_MAP[Binary] = 'Bytes' -def empty_rset(rql, args, rqlst=None): +def empty_rset(rql, args): """build an empty result set object""" - return ResultSet([], rql, args, rqlst=rqlst) + return ResultSet([], rql, args) # permission utilities ######################################################## @@ -167,7 +162,6 @@ # various resource accesors self.querier = querier self.schema = querier.schema - self.sqlannotate = querier.sqlgen_annotate self.rqlhelper = cnx.vreg.rqlhelper def annotate_rqlst(self): @@ -219,7 +213,7 @@ noinvariant = () if cached is None: self.rqlhelper.simplify(union) - self.sqlannotate(union) + self.querier.sqlgen_annotate(union) set_qdata(self.schema.rschema, union, noinvariant) if union.has_text_query: self.cache_key = None @@ -443,13 +437,13 @@ relations = {} for subj, rtype, obj in self.relation_defs(): # if a string is given into args instead of an int, we get it here - if isinstance(subj, string_types): + if isinstance(subj, str): subj = int(subj) - elif not isinstance(subj, integer_types): + elif not isinstance(subj, int): subj = subj.entity.eid - if isinstance(obj, string_types): + if isinstance(obj, str): obj = int(obj) - elif not isinstance(obj, integer_types): + elif not isinstance(obj, int): obj = obj.entity.eid if repo.schema.rschema(rtype).inlined: if subj not in edited_entities: @@ -481,10 +475,8 @@ def set_schema(self, schema): self.schema = schema self.clear_caches() - rqlhelper = self._repo.vreg.rqlhelper - self._annotate = rqlhelper.annotate # rql planner - self._planner = SSPlanner(schema, rqlhelper) + self._planner = SSPlanner(schema, self._repo.vreg.rqlhelper) # sql generation annotator self.sqlgen_annotate = SQLGenAnnotator(schema).annotate @@ -550,7 +542,7 @@ # Rewrite computed relations rewriter = RQLRelationRewriter(cnx) rewriter.rewrite(rqlst, args) - self._annotate(rqlst) + self._repo.vreg.rqlhelper.annotate(rqlst) if args: # different SQL generated when some argument is None or not (IS # NULL). This should be considered when computing sql cache key @@ -626,7 +618,7 @@ def parse(rql, annotate=False, parse=rqlhelper.parse): """Return a freshly parsed syntax tree for the given RQL.""" try: - return parse(text_type(rql), annotate=annotate) + return parse(rql, annotate=annotate) except UnicodeError: raise RQLSyntaxError(rql) self._parse = parse diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/repository.py --- a/cubicweb/server/repository.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/repository.py Fri May 24 16:29:14 2019 +0200 @@ -26,17 +26,12 @@ * handles session management """ -from __future__ import print_function - -from warnings import warn from itertools import chain from contextlib import contextmanager from logging import getLogger - -from six.moves import range, queue +import queue from logilab.common.decorators import cached, clear_cache -from logilab.common.deprecation import deprecated from yams import BadSchemaDefinition from rql.utils import rqlvar_maker @@ -323,14 +318,6 @@ for sourceent, source in self._sources()) return mapping - @property - @deprecated("[3.25] use source_by_eid()") - def sources_by_eid(self): - mapping = {self.system_source.eid: self.system_source} - mapping.update((sourceent.eid, source) - for sourceent, source in self._sources()) - return mapping - def _sources(self): if self.config.quick_start: return @@ -516,31 +503,6 @@ # public (dbapi) interface ################################################ - @deprecated("[3.19] use _cw.call_service('repo_stats')") - def stats(self): # XXX restrict to managers session? - """Return a dictionary containing some statistics about the repository - resources usage. - - This is a public method, not requiring a session id. - - This method is deprecated in favor of using _cw.call_service('repo_stats') - """ - with self.internal_cnx() as cnx: - return cnx.call_service('repo_stats') - - @deprecated("[3.19] use _cw.call_service('repo_gc_stats')") - def gc_stats(self, nmax=20): - """Return a dictionary containing some statistics about the repository - memory usage. - - This is a public method, not requiring a session id. - - nmax is the max number of (most) referenced object returned as - the 'referenced' result - """ - with self.internal_cnx() as cnx: - return cnx.call_service('repo_gc_stats', nmax=nmax) - def get_schema(self): """Return the instance schema. @@ -561,16 +523,11 @@ cubes.remove('cubicweb') return cubes - def get_option_value(self, option, foreid=None): + def get_option_value(self, option): """Return the value for `option` in the configuration. This is a public method, not requiring a session id. - - `foreid` argument is deprecated and now useless (as of 3.19). """ - if foreid is not None: - warn('[3.19] foreid argument is deprecated', DeprecationWarning, - stacklevel=2) # XXX we may want to check we don't give sensible information return self.config[option] @@ -631,17 +588,6 @@ 'P pkey K, P value V, NOT P for_user U', build_descr=False) - @deprecated("[3.19] Use session.call_service('register_user') instead'") - def register_user(self, login, password, email=None, **kwargs): - """check a user with the given login exists, if not create it with the - given password. This method is designed to be used for anonymous - registration on public web site. - """ - with self.internal_cnx() as cnx: - cnx.call_service('register_user', login=login, password=password, - email=email, **kwargs) - cnx.commit() - def find_users(self, fetch_attrs, **query_attrs): """yield user attributes for cwusers matching the given query_attrs (the result set cannot survive this method call) @@ -874,10 +820,6 @@ # operation (register pending eids before actual deletion to avoid # multiple call to glob_delete_entities) op = hook.CleanupDeletedEidsCacheOp.get_instance(cnx) - if not isinstance(eids, (set, frozenset)): - warn('[3.13] eids should be given as a set', DeprecationWarning, - stacklevel=2) - eids = frozenset(eids) eids = eids - op._container op._container |= eids data_by_etype = {} # values are [list of entities] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/rqlannotation.py --- a/cubicweb/server/rqlannotation.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/rqlannotation.py Fri May 24 16:29:14 2019 +0200 @@ -19,138 +19,11 @@ code generation. """ -from __future__ import print_function - from rql import BadRQLQuery from rql.nodes import Relation, VariableRef, Constant, Variable, Or from rql.utils import common_parent -def _annotate_select(annotator, rqlst): - has_text_query = False - for subquery in rqlst.with_: - if annotator._annotate_union(subquery.query): - has_text_query = True - getrschema = annotator.schema.rschema - for var in rqlst.defined_vars.values(): - stinfo = var.stinfo - if stinfo.get('ftirels'): - has_text_query = True - if stinfo['attrvar']: - stinfo['invariant'] = False - stinfo['principal'] = _select_main_var(stinfo['rhsrelations']) - continue - if stinfo['typerel'] is None: - # those particular queries should be executed using the system - # entities table unless there is some type restriction - if not stinfo['relations']: - # Any X, Any MAX(X)... - stinfo['invariant'] = True - stinfo['principal'] = None - continue - if (any(rel for rel in stinfo['relations'] - if rel.r_type == 'eid' and rel.operator() != '=') - and not any(r for r in var.stinfo['relations'] - var.stinfo['rhsrelations'] - if r.r_type != 'eid' - and (getrschema(r.r_type).inlined or getrschema(r.r_type).final))): - # Any X WHERE X eid > 2 - stinfo['invariant'] = True - stinfo['principal'] = None - continue - if stinfo['selected'] and var.valuable_references() == 1 + bool(stinfo['constnode']): - # "Any X", "Any X, Y WHERE X attr Y" - stinfo['invariant'] = False - continue - joins = set() - invariant = False - for ref in var.references(): - rel = ref.relation() - if rel is None or rel.is_types_restriction(): - continue - lhs, rhs = rel.get_parts() - onlhs = ref is lhs - role = 'subject' if onlhs else 'object' - if rel.r_type == 'eid': - if not (onlhs and len(stinfo['relations']) > 1): - break - if not stinfo['constnode']: - joins.add((rel, role)) - continue - elif rel.r_type == 'identity': - # identity can't be used as principal, so check other relation are used - # XXX explain rhs.operator == '=' - if rhs.operator != '=' or len(stinfo['relations']) <= 1: - break - joins.add((rel, role)) - continue - rschema = getrschema(rel.r_type) - if rel.optional: - if rel in stinfo.get('optrelations', ()): - # optional variable can't be invariant if this is the lhs - # variable of an inlined relation - if rel not in stinfo['rhsrelations'] and rschema.inlined: - break - # variable used as main variable of an optional relation can't - # be invariant, unless we can use some other relation as - # reference for the outer join - elif not stinfo['constnode']: - break - elif len(stinfo['relations']) == 2: - if onlhs: - ostinfo = rhs.children[0].variable.stinfo - else: - ostinfo = lhs.variable.stinfo - if not (ostinfo.get('optcomparisons') - or any(orel for orel in ostinfo['relations'] - if orel.optional and orel is not rel)): - break - if rschema.final or (onlhs and rschema.inlined): - if rschema.type != 'has_text': - # need join anyway if the variable appears in a final or - # inlined relation - break - joins.add((rel, role)) - continue - if not stinfo['constnode']: - if rschema.inlined and rel.neged(strict=True): - # if relation is inlined, can't be invariant if that - # variable is used anywhere else. - # see 'Any P WHERE NOT N ecrit_par P, N eid 512': - # sql for 'NOT N ecrit_par P' is 'N.ecrit_par is NULL' so P - # can use N.ecrit_par as principal - if (stinfo['selected'] or len(stinfo['relations']) > 1): - break - joins.add((rel, role)) - else: - # if there is at least one ambigous relation and no other to - # restrict types, can't be invariant since we need to filter out - # other types - if not annotator.is_ambiguous(var): - invariant = True - stinfo['invariant'] = invariant - if invariant and joins: - # remember rqlst/solutions analyze information - # we have to select a kindof "main" relation which will "extrajoins" - # the other - # priority should be given to relation which are not in inner queries - # (eg exists) - try: - stinfo['principal'] = principal = _select_principal(var.scope, joins) - if getrschema(principal.r_type).inlined: - # the scope of the lhs variable must be equal or outer to the - # rhs variable's scope (since it's retrieved from lhs's table) - sstinfo = principal.children[0].variable.stinfo - sstinfo['scope'] = common_parent(sstinfo['scope'], stinfo['scope']).scope - except CantSelectPrincipal: - stinfo['invariant'] = False - # see unittest_rqlannotation. test_has_text_security_cache_bug - # XXX probably more to do, but yet that work without more... - for col_alias in rqlst.aliases.values(): - if col_alias.stinfo.get('ftirels'): - has_text_query = True - return has_text_query - - class CantSelectPrincipal(Exception): """raised when no 'principal' variable can be found""" @@ -245,6 +118,7 @@ class SQLGenAnnotator(object): + def __init__(self, schema): self.schema = schema self.nfdomain = frozenset(eschema.type for eschema in schema.entities() @@ -255,8 +129,8 @@ job (read sql generation) a variable is tagged as invariant if: - * it's a non final variable - * it's not used as lhs in any final or inlined relation + * it is a non final variable + * it is not used as lhs in any final or inlined relation * there is no type restriction on this variable (either explicit in the syntax tree or because a solution for this variable has been removed due to security filtering) @@ -267,7 +141,132 @@ def _annotate_union(self, union): has_text_query = False for select in union.children: - if _annotate_select(self, select): + if self._annotate_select(select): + has_text_query = True + return has_text_query + + def _annotate_select(self, rqlst): + has_text_query = False + for subquery in rqlst.with_: + if self._annotate_union(subquery.query): + has_text_query = True + getrschema = self.schema.rschema + for var in rqlst.defined_vars.values(): + stinfo = var.stinfo + if stinfo.get('ftirels'): + has_text_query = True + if stinfo['attrvar']: + stinfo['invariant'] = False + stinfo['principal'] = _select_main_var(stinfo['rhsrelations']) + continue + if stinfo['typerel'] is None: + # those particular queries should be executed using the system + # entities table unless there is some type restriction + if not stinfo['relations']: + # Any X, Any MAX(X)... + stinfo['invariant'] = True + stinfo['principal'] = None + continue + if (any(rel for rel in stinfo['relations'] + if rel.r_type == 'eid' and rel.operator() != '=') + and not any(r for r in var.stinfo['relations'] - var.stinfo['rhsrelations'] + if r.r_type != 'eid' + and (getrschema(r.r_type).inlined + or getrschema(r.r_type).final))): + # Any X WHERE X eid > 2 + stinfo['invariant'] = True + stinfo['principal'] = None + continue + if stinfo['selected'] and var.valuable_references() == 1 + bool(stinfo['constnode']): + # "Any X", "Any X, Y WHERE X attr Y" + stinfo['invariant'] = False + continue + joins = set() + invariant = False + for ref in var.references(): + rel = ref.relation() + if rel is None or rel.is_types_restriction(): + continue + lhs, rhs = rel.get_parts() + onlhs = ref is lhs + role = 'subject' if onlhs else 'object' + if rel.r_type == 'eid': + if not (onlhs and len(stinfo['relations']) > 1): + break + if not stinfo['constnode']: + joins.add((rel, role)) + continue + elif rel.r_type == 'identity': + # identity can't be used as principal, so check other relation are used + # XXX explain rhs.operator == '=' + if rhs.operator != '=' or len(stinfo['relations']) <= 1: + break + joins.add((rel, role)) + continue + rschema = getrschema(rel.r_type) + if rel.optional: + if rel in stinfo.get('optrelations', ()): + # optional variable can't be invariant if this is the lhs + # variable of an inlined relation + if rel not in stinfo['rhsrelations'] and rschema.inlined: + break + # variable used as main variable of an optional relation can't + # be invariant, unless we can use some other relation as + # reference for the outer join + elif not stinfo['constnode']: + break + elif len(stinfo['relations']) == 2: + if onlhs: + ostinfo = rhs.children[0].variable.stinfo + else: + ostinfo = lhs.variable.stinfo + if not (ostinfo.get('optcomparisons') + or any(orel for orel in ostinfo['relations'] + if orel.optional and orel is not rel)): + break + if rschema.final or (onlhs and rschema.inlined): + if rschema.type != 'has_text': + # need join anyway if the variable appears in a final or + # inlined relation + break + joins.add((rel, role)) + continue + if not stinfo['constnode']: + if rschema.inlined and rel.neged(strict=True): + # if relation is inlined, can't be invariant if that + # variable is used anywhere else. + # see 'Any P WHERE NOT N ecrit_par P, N eid 512': + # sql for 'NOT N ecrit_par P' is 'N.ecrit_par is NULL' so P + # can use N.ecrit_par as principal + if (stinfo['selected'] or len(stinfo['relations']) > 1): + break + joins.add((rel, role)) + else: + # if there is at least one ambigous relation and no other to + # restrict types, can't be invariant since we need to filter out + # other types + if not self.is_ambiguous(var): + invariant = True + stinfo['invariant'] = invariant + if invariant and joins: + # remember rqlst/solutions analyze information + # we have to select a kindof "main" relation which will "extrajoins" + # the other + # priority should be given to relation which are not in inner queries + # (eg exists) + try: + stinfo['principal'] = principal = _select_principal(var.scope, joins) + if getrschema(principal.r_type).inlined: + # the scope of the lhs variable must be equal or outer to the + # rhs variable's scope (since it's retrieved from lhs's table) + sstinfo = principal.children[0].variable.stinfo + sstinfo['scope'] = common_parent(sstinfo['scope'], stinfo['scope']).scope + except CantSelectPrincipal: + stinfo['invariant'] = False + # see unittest_rqlannotation. test_has_text_security_cache_bug + # XXX probably more to do, but yet that work without more... + for col_alias in rqlst.aliases.values(): + if col_alias.stinfo.get('ftirels'): has_text_query = True return has_text_query diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/schema2sql.py --- a/cubicweb/server/schema2sql.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/schema2sql.py Fri May 24 16:29:14 2019 +0200 @@ -19,14 +19,13 @@ from hashlib import md5 -from six import string_types, text_type -from six.moves import range - from yams.constraints import (SizeConstraint, UniqueConstraint, Attribute, NOW, TODAY) from logilab import database from logilab.common.decorators import monkeypatch +from cubicweb.schema import constraint_name_for + # default are usually not handled at the sql level. If you want them, set # SET_DEFAULT to True SET_DEFAULT = False @@ -86,9 +85,9 @@ given attributes of the entity schema (actually, the later may be a schema or a string). """ # keep giving eschema instead of table name for bw compat - table = text_type(eschema) + table = str(eschema) # unique_index_name is used as name of CWUniqueConstraint, hence it should be unicode - return text_type(build_index_name(table, attrs, 'unique_')) + return build_index_name(table, attrs, 'unique_') def iter_unique_index_names(eschema): @@ -187,7 +186,7 @@ constraint. Maybe (None, None) if the constraint is not handled in the backend. """ attr = rdef.rtype.type - cstrname = constraint.name_for(rdef) + cstrname = constraint_name_for(constraint, rdef) if constraint.type() == 'BoundaryConstraint': value = constraint_value_as_sql(constraint.boundary, dbhelper, prefix) return cstrname, '%s%s %s %s' % (prefix, attr, constraint.operator, value) @@ -202,7 +201,7 @@ return cstrname, ' AND '.join(condition) elif constraint.type() == 'StaticVocabularyConstraint': sample = next(iter(constraint.vocabulary())) - if not isinstance(sample, string_types): + if not isinstance(sample, str): values = ', '.join(str(word) for word in constraint.vocabulary()) else: # XXX better quoting? diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/schemaserial.py --- a/cubicweb/server/schemaserial.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/schemaserial.py Fri May 24 16:29:14 2019 +0200 @@ -17,20 +17,16 @@ # with CubicWeb. If not, see . """functions for schema / permissions (de)serialization using RQL""" -from __future__ import print_function - import json import sys import sqlite3 -from six import PY2, text_type, string_types - from logilab.common.shellutils import ProgressBar, DummyProgressBar from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo, constraints -from cubicweb import Binary -from cubicweb.schema import (KNOWN_RPROPERTIES, CONSTRAINTS, ETYPE_NAME_MAP, +from cubicweb import Binary, ETYPE_NAME_MAP +from cubicweb.schema import (KNOWN_RPROPERTIES, CONSTRAINTS, VIRTUAL_RTYPES) from cubicweb.server import sqlutils, schema2sql as y2sql @@ -378,7 +374,7 @@ cstrtypemap = {} rql = 'INSERT CWConstraintType X: X name %(ct)s' for cstrtype in CONSTRAINTS: - cstrtypemap[cstrtype] = execute(rql, {'ct': text_type(cstrtype)}, + cstrtypemap[cstrtype] = execute(rql, {'ct': cstrtype}, build_descr=False)[0][0] pb.update() # serialize relations @@ -483,7 +479,7 @@ for i, name in enumerate(unique_together): rschema = eschema.schema.rschema(name) rtype = 'T%d' % i - substs[rtype] = text_type(rschema.type) + substs[rtype] = rschema.type relations.append('C relations %s' % rtype) restrictions.append('%(rtype)s name %%(%(rtype)s)s' % {'rtype': rtype}) relations = ', '.join(relations) @@ -494,18 +490,10 @@ def _ervalues(erschema): - try: - type_ = text_type(erschema.type) - except UnicodeDecodeError as e: - raise Exception("can't decode %s [was %s]" % (erschema.type, e)) - try: - desc = text_type(erschema.description) or u'' - except UnicodeDecodeError as e: - raise Exception("can't decode %s [was %s]" % (erschema.description, e)) return { - 'name': type_, + 'name': erschema.type, 'final': erschema.final, - 'description': desc, + 'description': erschema.description, } # rtype serialization @@ -531,10 +519,7 @@ values['final'] = rschema.final values['symmetric'] = rschema.symmetric values['inlined'] = rschema.inlined - if PY2 and isinstance(rschema.fulltext_container, str): - values['fulltext_container'] = unicode(rschema.fulltext_container) - else: - values['fulltext_container'] = rschema.fulltext_container + values['fulltext_container'] = rschema.fulltext_container relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values @@ -547,7 +532,7 @@ def crschema_relations_values(crschema): values = _ervalues(crschema) - values['rule'] = text_type(crschema.rule) + values['rule'] = crschema.rule # XXX why oh why? del values['final'] relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] @@ -593,8 +578,6 @@ value = bool(value) elif prop == 'ordernum': value = int(value) - elif PY2 and isinstance(value, str): - value = unicode(value) if value is not None and prop == 'default': value = Binary.zpickle(value) values[amap.get(prop, prop)] = value @@ -606,7 +589,7 @@ def constraints2rql(cstrtypemap, constraints, rdefeid=None): for constraint in constraints: values = {'ct': cstrtypemap[constraint.type()], - 'value': text_type(constraint.serialize()), + 'value': constraint.serialize(), 'x': rdefeid} # when not specified, will have to be set by the caller yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \ CT eid %(ct)s, EDEF eid %(x)s', values @@ -625,7 +608,7 @@ # may occurs when modifying persistent schema continue for group_or_rqlexpr in grantedto: - if isinstance(group_or_rqlexpr, string_types): + if isinstance(group_or_rqlexpr, str): # group try: yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action, @@ -639,9 +622,9 @@ rqlexpr = group_or_rqlexpr yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, ' 'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action, - {'e': text_type(rqlexpr.expression), - 'v': text_type(','.join(sorted(rqlexpr.mainvars))), - 't': text_type(rqlexpr.__class__.__name__)}) + {'e': rqlexpr.expression, + 'v': ','.join(sorted(rqlexpr.mainvars)), + 't': rqlexpr.__class__.__name__}) # update functions @@ -653,7 +636,7 @@ def updaterschema2rql(rschema, eid): if rschema.rule: yield ('SET X rule %(r)s WHERE X eid %(x)s', - {'x': eid, 'r': text_type(rschema.rule)}) + {'x': eid, 'r': rschema.rule}) else: relations, values = rschema_relations_values(rschema) values['x'] = eid diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/serverconfig.py --- a/cubicweb/server/serverconfig.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/serverconfig.py Fri May 24 16:29:14 2019 +0200 @@ -16,15 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """server.serverconfig definition""" -from __future__ import print_function - - +from io import StringIO import sys from os.path import join, exists -from six.moves import StringIO - import logilab.common.configuration as lgconfig from logilab.common.decorators import cached diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/serverctl.py --- a/cubicweb/server/serverctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/serverctl.py Fri May 24 16:29:14 2019 +0200 @@ -16,18 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """cubicweb-ctl commands and command handlers specific to the repository""" -from __future__ import print_function - # *ctl module should limit the number of import to be imported as quickly as # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash # completion). So import locally in command helpers. +import sched import sys import os from contextlib import contextmanager -from six import string_types -from six.moves import input - from logilab.common.configuration import Configuration, merge_options from logilab.common.shellutils import ASK, generate_password @@ -606,7 +602,7 @@ {'short': 'p', 'type': 'string', 'metavar': '', 'default': None, 'help': 'Use this password instead of prompt for one.\n' - '/!\ THIS IS AN INSECURE PRACTICE /!\ \n' + '/!\\ THIS IS AN INSECURE PRACTICE /!\\ \n' 'the password will appear in shell history'} ), ) @@ -1006,12 +1002,11 @@ def run(self, args): from cubicweb.cwctl import init_cmdline_log_threshold from cubicweb.server.repository import Repository - from cubicweb.server.utils import scheduler config = ServerConfiguration.config_for(args[0]) # Log to stdout, since the this command runs in the foreground. config.global_set_option('log-file', None) init_cmdline_log_threshold(config, self['loglevel']) - repo = Repository(config, scheduler()) + repo = Repository(config, sched.scheduler()) repo.bootstrap() try: repo.run_scheduler() @@ -1095,8 +1090,7 @@ for p in ('read', 'add', 'update', 'delete'): rule = perms.get(p) if rule: - perms[p] = tuple(str(x) if isinstance(x, string_types) else x - for x in rule) + perms[p] = tuple(rule) return perms, perms in defaultrelperms or perms in defaulteperms diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/session.py --- a/cubicweb/server/session.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/session.py Fri May 24 16:29:14 2019 +0200 @@ -17,18 +17,12 @@ # with CubicWeb. If not, see . """Repository users' and internal' sessions.""" -from __future__ import print_function - import functools import sys from uuid import uuid4 -from warnings import warn from contextlib import contextmanager from logging import getLogger -from six import text_type - -from logilab.common.deprecation import deprecated from logilab.common.registry import objectify_predicate from cubicweb import QueryError, ProgrammingError, schema, server @@ -39,7 +33,6 @@ NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy() -NO_UNDO_TYPES.add('CWCache') NO_UNDO_TYPES.add('CWSession') NO_UNDO_TYPES.add('CWDataImport') # is / is_instance_of are usually added by sql hooks except when using @@ -73,15 +66,6 @@ return req.vreg.config.repairing -@deprecated('[3.17] use .allow/deny_all_hooks_but instead') -def hooks_control(obj, mode, *categories): - assert mode in (HOOKS_ALLOW_ALL, HOOKS_DENY_ALL) - if mode == HOOKS_ALLOW_ALL: - return obj.allow_all_hooks_but(*categories) - elif mode == HOOKS_DENY_ALL: - return obj.deny_all_hooks_but(*categories) - - class _hooks_control(object): """context manager to control activated hooks categories. @@ -123,11 +107,6 @@ self.cnx._hooks_categories = self.old_categories -@deprecated('[3.17] use .security_enabled instead') -def security_enabled(obj, *args, **kwargs): - return obj.security_enabled(*args, **kwargs) - - class _security_enabled(object): """context manager to control security w/ session.execute, @@ -390,11 +369,6 @@ # shared data handling ################################################### @property - @deprecated('[3.25] use transaction_data or req.session.data', stacklevel=3) - def data(self): - return self.transaction_data - - @property def rql_rewriter(self): return self._rewriter @@ -409,24 +383,6 @@ self.local_perm_cache.clear() self.rewriter = RQLRewriter(self) - @deprecated('[3.19] cnxset are automatically managed now.' - ' stop using explicit set and free.') - def set_cnxset(self): - pass - - @deprecated('[3.19] cnxset are automatically managed now.' - ' stop using explicit set and free.') - def free_cnxset(self, ignoremode=False): - pass - - @property - @contextmanager - @_open_only - @deprecated('[3.21] a cnxset is automatically set on __enter__ call now.' - ' stop using .ensure_cnx_set') - def ensure_cnx_set(self): - yield - # Entity cache management ################################################# # # The connection entity cache as held in cnx.transaction_data is removed at the @@ -681,7 +637,7 @@ def transaction_uuid(self, set=True): uuid = self.transaction_data.get('tx_uuid') if set and uuid is None: - self.transaction_data['tx_uuid'] = uuid = text_type(uuid4().hex) + self.transaction_data['tx_uuid'] = uuid = uuid4().hex self.repo.system_source.start_undoable_transaction(self, uuid) return uuid @@ -702,12 +658,6 @@ """Return entity type for the entity with id `eid`.""" return self.repo.type_from_eid(eid, self) - @deprecated('[3.24] use entity_type(eid) instead') - @_open_only - def entity_metas(self, eid): - """Return a dictionary {type}) for the entity with id `eid`.""" - return {'type': self.repo.type_from_eid(eid, self)} - # core method ############################################################# @_open_only @@ -721,14 +671,8 @@ return rset @_open_only - def rollback(self, free_cnxset=None, reset_pool=None): + def rollback(self): """rollback the current transaction""" - if free_cnxset is not None: - warn('[3.21] free_cnxset is now unneeded', - DeprecationWarning, stacklevel=2) - if reset_pool is not None: - warn('[3.13] reset_pool is now unneeded', - DeprecationWarning, stacklevel=2) cnxset = self.cnxset assert cnxset is not None try: @@ -747,14 +691,8 @@ self.clear() @_open_only - def commit(self, free_cnxset=None, reset_pool=None): + def commit(self): """commit the current session's transaction""" - if free_cnxset is not None: - warn('[3.21] free_cnxset is now unneeded', - DeprecationWarning, stacklevel=2) - if reset_pool is not None: - warn('[3.13] reset_pool is now unneeded', - DeprecationWarning, stacklevel=2) assert self.cnxset is not None cstate = self.commit_state if cstate == 'uncommitable': diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/__init__.py --- a/cubicweb/server/sources/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -17,13 +17,9 @@ # with CubicWeb. If not, see . """cubicweb server sources support""" -from __future__ import print_function - from time import time from logging import getLogger -from six import text_type - from logilab.common import configuration from logilab.common.textutils import unormalize @@ -97,7 +93,7 @@ self.uri = source_config.pop('uri') # unormalize to avoid non-ascii characters in logger's name, this will cause decoding error # on logging - set_log_methods(self, getLogger('cubicweb.sources.' + unormalize(text_type(self.uri)))) + set_log_methods(self, getLogger('cubicweb.sources.' + unormalize(self.uri))) source_config.pop('type') self.config = self._check_config_dict( eid, source_config, raise_on_error=False) @@ -155,7 +151,7 @@ except Exception as ex: if not raise_on_error: continue - msg = text_type(ex) + msg = str(ex) raise ValidationError(eid, {role_name('config', 'subject'): msg}) processed[optname] = value # cw < 3.10 bw compat diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/datafeed.py --- a/cubicweb/server/sources/datafeed.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/datafeed.py Fri May 24 16:29:14 2019 +0200 @@ -24,17 +24,14 @@ from os.path import exists from datetime import datetime, timedelta from functools import partial - -from six.moves.urllib.parse import urlparse -from six.moves.urllib.request import Request, build_opener, HTTPCookieProcessor -from six.moves.urllib.error import HTTPError -from six.moves.http_cookiejar import CookieJar +from http.cookiejar import CookieJar +from urllib.parse import urlparse +from urllib.request import Request, build_opener, HTTPCookieProcessor +from urllib.error import HTTPError from pytz import utc from lxml import etree -from logilab.common.deprecation import deprecated - from cubicweb import ObjectNotFound, ValidationError, SourceException, _ from cubicweb.server.sources import AbstractSource from cubicweb.appobject import AppObject @@ -365,20 +362,6 @@ class DataFeedXMLParser(DataFeedParser): - @deprecated() - def process(self, url, raise_on_error=False): - """IDataFeedParser main entry point""" - try: - parsed = self.parse(url) - except Exception as ex: - if raise_on_error: - raise - self.import_log.record_error(str(ex)) - return True - for args in parsed: - self.process_item(*args, raise_on_error=raise_on_error) - return False - def parse(self, url): stream = self.retrieve_url(url) return self.parse_etree(etree.parse(stream).getroot()) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/ldapfeed.py --- a/cubicweb/server/sources/ldapfeed.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/ldapfeed.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ from datetime import datetime -from six import PY2, string_types - import ldap3 from logilab.common.configuration import merge_options @@ -341,15 +339,13 @@ elif self.user_attrs.get(key) == 'modification_date': itemdict[key] = datetime.strptime(value[0], '%Y%m%d%H%M%SZ') else: - if PY2 and value and isinstance(value[0], str): - value = [unicode(val, 'utf-8', 'replace') for val in value] if len(value) == 1: itemdict[key] = value = value[0] else: itemdict[key] = value # we expect memberUid to be a list of user ids, make sure of it member = self.group_rev_attrs['member'] - if isinstance(itemdict.get(member), string_types): + if isinstance(itemdict.get(member), str): itemdict[member] = [itemdict[member]] return itemdict diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/native.py --- a/cubicweb/server/sources/native.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/native.py Fri May 24 16:29:14 2019 +0200 @@ -17,21 +17,17 @@ # with CubicWeb. If not, see . """Adapters for native cubicweb sources.""" -from __future__ import print_function - from threading import Lock from datetime import datetime from contextlib import contextmanager from os.path import basename +import pickle import re import itertools import zipfile import logging import sys -from six import PY2, text_type, string_types -from six.moves import range, cPickle as pickle, zip - from logilab.common.decorators import cached, clear_cache from logilab.common.configuration import Method from logilab.common.shellutils import getlogin, ASK @@ -121,12 +117,11 @@ class _UndoException(Exception): """something went wrong during undoing""" - def __unicode__(self): + def __str__(self): """Called by the unicode builtin; should return a Unicode object Type of _UndoException message must be `unicode` by design in CubicWeb. """ - assert isinstance(self.args[0], text_type) return self.args[0] @@ -526,7 +521,7 @@ sql, qargs, cbs = self._rql_sqlgen.generate(union, args) self._cache[cachekey] = sql, qargs, cbs args = self.merge_args(args, qargs) - assert isinstance(sql, string_types), repr(sql) + assert isinstance(sql, str), repr(sql) cursor = cnx.system_sql(sql, args) results = self.process_result(cursor, cnx, cbs) assert dbg_results(results) @@ -581,7 +576,7 @@ self.doexec(cnx, sql, attrs) if cnx.ertype_supports_undo(entity.cw_etype): self._record_tx_action(cnx, 'tx_entity_actions', u'C', - etype=text_type(entity.cw_etype), eid=entity.eid) + etype=entity.cw_etype, eid=entity.eid) def update_entity(self, cnx, entity): """replace an entity in the source""" @@ -590,7 +585,7 @@ if cnx.ertype_supports_undo(entity.cw_etype): changes = self._save_attrs(cnx, entity, attrs) self._record_tx_action(cnx, 'tx_entity_actions', u'U', - etype=text_type(entity.cw_etype), eid=entity.eid, + etype=entity.cw_etype, eid=entity.eid, changes=self._binary(pickle.dumps(changes))) sql = self.sqlgen.update(SQL_PREFIX + entity.cw_etype, attrs, ['cw_eid']) @@ -605,7 +600,7 @@ if (r.final or r.inlined) and r not in VIRTUAL_RTYPES] changes = self._save_attrs(cnx, entity, attrs) self._record_tx_action(cnx, 'tx_entity_actions', u'D', - etype=text_type(entity.cw_etype), eid=entity.eid, + etype=entity.cw_etype, eid=entity.eid, changes=self._binary(pickle.dumps(changes))) attrs = {'cw_eid': entity.eid} sql = self.sqlgen.delete(SQL_PREFIX + entity.cw_etype, attrs) @@ -616,7 +611,7 @@ self._add_relations(cnx, rtype, [(subject, object)], inlined) if cnx.ertype_supports_undo(rtype): self._record_tx_action(cnx, 'tx_relation_actions', u'A', - eid_from=subject, rtype=text_type(rtype), eid_to=object) + eid_from=subject, rtype=rtype, eid_to=object) def add_relations(self, cnx, rtype, subj_obj_list, inlined=False): """add a relations to the source""" @@ -624,7 +619,7 @@ if cnx.ertype_supports_undo(rtype): for subject, object in subj_obj_list: self._record_tx_action(cnx, 'tx_relation_actions', u'A', - eid_from=subject, rtype=text_type(rtype), eid_to=object) + eid_from=subject, rtype=rtype, eid_to=object) def _add_relations(self, cnx, rtype, subj_obj_list, inlined=False): """add a relation to the source""" @@ -656,7 +651,7 @@ self._delete_relation(cnx, subject, rtype, object, rschema.inlined) if cnx.ertype_supports_undo(rtype): self._record_tx_action(cnx, 'tx_relation_actions', u'R', - eid_from=subject, rtype=text_type(rtype), eid_to=object) + eid_from=subject, rtype=rtype, eid_to=object) def _delete_relation(self, cnx, subject, rtype, object, inlined=False): """delete a relation from the source""" @@ -721,14 +716,17 @@ mo = re.search(r'\bcstr[a-f0-9]{32}\b', arg) if mo is not None: # postgresql - raise ViolatedConstraint(cnx, cstrname=mo.group(0)) + raise ViolatedConstraint(cnx, cstrname=mo.group(0), + query=query) if arg.startswith('CHECK constraint failed:'): # sqlite3 (new) - raise ViolatedConstraint(cnx, cstrname=arg.split(':', 1)[1].strip()) + raise ViolatedConstraint(cnx, cstrname=arg.split(':', 1)[1].strip(), + query=query) mo = re.match('^constraint (cstr.*) failed$', arg) if mo is not None: # sqlite3 (old) - raise ViolatedConstraint(cnx, cstrname=mo.group(1)) + raise ViolatedConstraint(cnx, cstrname=mo.group(1), + query=query) raise return cursor @@ -833,7 +831,7 @@ """add type and source info for an eid into the system table""" assert cnx.cnxset is not None # begin by inserting eid/type/source into the entities table - attrs = {'type': text_type(entity.cw_etype), 'eid': entity.eid} + attrs = {'type': entity.cw_etype, 'eid': entity.eid} self._handle_insert_entity_sql(cnx, self.sqlgen.insert('entities', attrs), attrs) # insert core relations: is, is_instance_of and cw_source @@ -908,7 +906,7 @@ # only, and with no eid specified assert actionfilters.get('action', 'C') in 'CUD' assert 'eid' not in actionfilters - tearestr['etype'] = text_type(val) + tearestr['etype'] = val elif key == 'eid': # eid filter may apply to 'eid' of tx_entity_actions or to # 'eid_from' OR 'eid_to' of tx_relation_actions @@ -919,10 +917,10 @@ trarestr['eid_to'] = val elif key == 'action': if val in 'CUD': - tearestr['txa_action'] = text_type(val) + tearestr['txa_action'] = val else: assert val in 'AR' - trarestr['txa_action'] = text_type(val) + trarestr['txa_action'] = val else: raise AssertionError('unknow filter %s' % key) assert trarestr or tearestr, "can't only filter on 'public'" @@ -956,11 +954,10 @@ def tx_info(self, cnx, txuuid): """See :class:`cubicweb.repoapi.Connection.transaction_info`""" - return tx.Transaction(cnx, txuuid, *self._tx_info(cnx, text_type(txuuid))) + return tx.Transaction(cnx, txuuid, *self._tx_info(cnx, txuuid)) def tx_actions(self, cnx, txuuid, public): """See :class:`cubicweb.repoapi.Connection.transaction_actions`""" - txuuid = text_type(txuuid) self._tx_info(cnx, txuuid) restr = {'tx_uuid': txuuid} if public: @@ -1093,8 +1090,6 @@ elif eschema.destination(rtype) in ('Bytes', 'Password'): changes[column] = self._binary(value) edited[rtype] = Binary(value) - elif PY2 and isinstance(value, str): - edited[rtype] = text_type(value, cnx.encoding, 'replace') else: edited[rtype] = value # This must only be done after init_entitiy_caches : defered in calling functions @@ -1134,14 +1129,14 @@ try: sentity, oentity, rdef = _undo_rel_info(cnx, subj, rtype, obj) except _UndoException as ex: - errors.append(text_type(ex)) + errors.append(str(ex)) else: for role, entity in (('subject', sentity), ('object', oentity)): try: _undo_check_relation_target(entity, rdef, role) except _UndoException as ex: - errors.append(text_type(ex)) + errors.append(str(ex)) continue if not errors: self.repo.hm.call_hooks('before_add_relation', cnx, @@ -1216,7 +1211,7 @@ try: sentity, oentity, rdef = _undo_rel_info(cnx, subj, rtype, obj) except _UndoException as ex: - errors.append(text_type(ex)) + errors.append(str(ex)) else: rschema = rdef.rtype if rschema.inlined: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/rql2sql.py --- a/cubicweb/server/sources/rql2sql.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/rql2sql.py Fri May 24 16:29:14 2019 +0200 @@ -49,9 +49,6 @@ import threading -from six import PY2, text_type -from six.moves import range - from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY from rql import BadRQLQuery, CoercionError @@ -1517,8 +1514,6 @@ return self.keyword_map[value]() if constant.type == 'Substitute': _id = value - if PY2 and isinstance(_id, text_type): - _id = _id.encode() else: _id = str(id(constant)).replace('-', '', 1) self._query_attrs[_id] = value diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sources/storages.py --- a/cubicweb/server/sources/storages.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sources/storages.py Fri May 24 16:29:14 2019 +0200 @@ -23,8 +23,6 @@ from contextlib import contextmanager import tempfile -from six import PY2, PY3, text_type, binary_type - from logilab.common import nullobject from yams.schema import role_name @@ -113,15 +111,8 @@ class BytesFileSystemStorage(Storage): """store Bytes attribute value on the file system""" def __init__(self, defaultdir, fsencoding=_marker, wmode=0o444): - if PY3: - if not isinstance(defaultdir, text_type): - raise TypeError('defaultdir must be a unicode object in python 3') - if fsencoding is not _marker: - raise ValueError('fsencoding is no longer supported in python 3') - else: - self.fsencoding = fsencoding or 'utf-8' - if isinstance(defaultdir, text_type): - defaultdir = defaultdir.encode(fsencoding) + if fsencoding is not _marker: + raise ValueError('fsencoding is no longer supported in python 3') self.default_directory = defaultdir # extra umask to use when creating file # 0444 as in "only allow read bit in permission" @@ -160,7 +151,7 @@ if binary is not None: fd, fpath = self.new_fs_path(entity, attr) # bytes storage used to store file's path - binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8')) + binary_obj = Binary(fpath.encode('utf-8')) entity.cw_edited.edited_attribute(attr, binary_obj) self._writecontent(fd, binary) AddFileOp.get_instance(entity._cw).add_data(fpath) @@ -204,7 +195,7 @@ entity.cw_edited.edited_attribute(attr, None) else: # register the new location for the file. - binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8')) + binary_obj = Binary(fpath.encode('utf-8')) entity.cw_edited.edited_attribute(attr, binary_obj) if oldpath is not None and oldpath != fpath: # Mark the old file as useless so the file will be removed at @@ -224,19 +215,17 @@ # available. Keeping the extension is useful for example in the case of # PIL processing that use filename extension to detect content-type, as # well as providing more understandable file names on the fs. - if PY2: - attr = attr.encode('ascii') basename = [str(entity.eid), attr] name = entity.cw_attr_metadata(attr, 'name') if name is not None: - basename.append(name.encode(self.fsencoding) if PY2 else name) + basename.append(name) fd, fspath = uniquify_path(self.default_directory, '_'.join(basename)) if fspath is None: msg = entity._cw._('failed to uniquify path (%s, %s)') % ( self.default_directory, '_'.join(basename)) raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg}) - assert isinstance(fspath, str) # bytes on py2, unicode on py3 + assert isinstance(fspath, str) return fd, fspath def current_fs_path(self, entity, attr): @@ -251,11 +240,8 @@ if rawvalue is None: # no previous value return None fspath = sysource._process_value(rawvalue, cu.description[0], - binarywrap=binary_type) - if PY3: - fspath = fspath.decode('utf-8') - assert isinstance(fspath, str) # bytes on py2, unicode on py3 - return fspath + binarywrap=bytes) + return fspath.decode('utf-8') def migrate_entity(self, entity, attribute): """migrate an entity attribute to the storage""" @@ -274,7 +260,7 @@ class AddFileOp(hook.DataOperationMixIn, hook.Operation): def rollback_event(self): for filepath in self.get_data(): - assert isinstance(filepath, str) # bytes on py2, unicode on py3 + assert isinstance(filepath, str) try: unlink(filepath) except Exception as ex: @@ -283,7 +269,7 @@ class DeleteFileOp(hook.DataOperationMixIn, hook.Operation): def postcommit_event(self): for filepath in self.get_data(): - assert isinstance(filepath, str) # bytes on py2, unicode on py3 + assert isinstance(filepath, str) try: unlink(filepath) except Exception as ex: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/sqlutils.py --- a/cubicweb/server/sqlutils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/sqlutils.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """SQL utilities functions and classes.""" -from __future__ import print_function - import os import sys import re @@ -27,14 +25,10 @@ from logging import getLogger from datetime import time, datetime, timedelta -from six import string_types, text_type -from six.moves import filter - from pytz import utc from logilab import database as db, common as lgc from logilab.common.shellutils import ProgressBar, DummyProgressBar -from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods from logilab.common.date import utctime, utcdatetime, strptime from logilab.database.sqlgen import SQLGenerator @@ -53,7 +47,7 @@ env = os.environ.copy() for key, value in (extra_env or {}).items(): env.setdefault(key, value) - if isinstance(cmd, string_types): + if isinstance(cmd, str): print(cmd) return subprocess.call(cmd, shell=True, env=env) else: @@ -82,7 +76,7 @@ else: execute = cursor_or_execute sqlstmts_as_string = False - if isinstance(sqlstmts, string_types): + if isinstance(sqlstmts, str): sqlstmts_as_string = True sqlstmts = sqlstmts.split(delimiter) if withpb: @@ -237,21 +231,6 @@ self.cnx = self._source.get_connection() self.cu = self.cnx.cursor() - @deprecated('[3.19] use .cu instead') - def __getitem__(self, uri): - assert uri == 'system' - return self.cu - - @deprecated('[3.19] use repo.system_source instead') - def source(self, uid): - assert uid == 'system' - return self._source - - @deprecated('[3.19] use .cnx instead') - def connection(self, uid): - assert uid == 'system' - return self.cnx - class SqliteConnectionWrapper(ConnectionWrapper): """Sqlite specific connection wrapper: close the connection each time it's @@ -491,7 +470,7 @@ for row, rowdesc in zip(rset, rset.description): for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)): if vtype in ('TZDatetime', 'Date', 'Datetime') \ - and isinstance(value, text_type): + and isinstance(value, str): found_date = True value = value.rsplit('.', 1)[0] try: @@ -500,7 +479,7 @@ row[cellindex] = strptime(value, '%Y-%m-%d') if vtype == 'TZDatetime': row[cellindex] = row[cellindex].replace(tzinfo=utc) - if vtype == 'Time' and isinstance(value, text_type): + if vtype == 'Time' and isinstance(value, str): found_date = True try: row[cellindex] = strptime(value, '%H:%M:%S') @@ -533,7 +512,7 @@ self.values.add(value) def finalize(self): - return ', '.join(text_type(v) for v in self.values) + return ', '.join(str(v) for v in self.values) cnx.create_aggregate("GROUP_CONCAT", 1, group_concat) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/ssplanner.py --- a/cubicweb/server/ssplanner.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/ssplanner.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """plan execution of rql queries on a single source""" -from six import text_type - from rql.stmts import Union, Select from rql.nodes import Constant, Relation @@ -55,7 +53,7 @@ value = rhs.eval(plan.args) eschema = edef.entity.e_schema attrtype = eschema.subjrels[rtype].objects(eschema)[0] - if attrtype == 'Password' and isinstance(value, text_type): + if attrtype == 'Password' and isinstance(value, str): value = value.encode('UTF8') edef.edited_attribute(rtype, value) elif str(rhs) in to_build: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/__init__.py --- a/cubicweb/server/test/data-migractions/cubes/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakecustomtype/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakecustomtype/__pkginfo__.py --- a/cubicweb/server/test/data-migractions/cubes/fakecustomtype/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# pylint: disable-msg=W0622 -"""cubicweb-fakeemail packaging information""" - -modname = 'fakecustomtype' -distname = "cubicweb-%s" % modname - -numversion = (1, 0, 0) -version = '.'.join(str(num) for num in numversion) - -license = 'LGPL' -author = "Logilab" -author_email = "contact@logilab.fr" -web = 'http://www.cubicweb.org/project/%s' % distname -description = "whatever" -classifiers = [ - 'Environment :: Web Environment', - 'Framework :: CubicWeb', - 'Programming Language :: Python', - 'Programming Language :: JavaScript', -] - -# used packages -__depends__ = {'cubicweb': '>= 3.19.0', - } - - -# packaging ### - -from os import listdir as _listdir -from os.path import join, isdir -from glob import glob - -THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname) - -def listdir(dirpath): - return [join(dirpath, fname) for fname in _listdir(dirpath) - if fname[0] != '.' and not fname.endswith('.pyc') - and not fname.endswith('~') - and not isdir(join(dirpath, fname))] - -data_files = [ - # common files - [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], - ] -# check for possible extended cube layout -for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration', 'wdoc'): - if isdir(dirname): - data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) -# Note: here, you'll need to add subdirectories if you want -# them to be included in the debian package diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakecustomtype/schema.py --- a/cubicweb/server/test/data-migractions/cubes/fakecustomtype/schema.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ - -from yams.buildobjs import EntityType, make_type - -Numeric = make_type('Numeric') - -class Location(EntityType): - num = Numeric(scale=10, precision=18) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakecustomtype/site_cubicweb.py --- a/cubicweb/server/test/data-migractions/cubes/fakecustomtype/site_cubicweb.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -from yams import register_base_type -from logilab.database import get_db_helper -from logilab.database.sqlgen import SQLExpression - -_NUMERIC_PARAMETERS = {'scale': 0, 'precision': None} -register_base_type('Numeric', _NUMERIC_PARAMETERS) - -# Add the datatype to the helper mapping -pghelper = get_db_helper('postgres') - - -def pg_numeric_sqltype(rdef): - """Return a PostgreSQL column type corresponding to rdef - """ - return 'numeric(%s, %s)' % (rdef.precision, rdef.scale) - -pghelper.TYPE_MAPPING['Numeric'] = pg_numeric_sqltype diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakeemail/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakeemail/__pkginfo__.py --- a/cubicweb/server/test/data-migractions/cubes/fakeemail/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# pylint: disable-msg=W0622 -"""cubicweb-fakeemail packaging information""" - -modname = 'fakeemail' -distname = "cubicweb-%s" % modname - -numversion = (1, 10, 0) -version = '.'.join(str(num) for num in numversion) - -license = 'LGPL' -author = "Logilab" -author_email = "contact@logilab.fr" -web = 'http://www.cubicweb.org/project/%s' % distname -description = "email component for the CubicWeb framework" -classifiers = [ - 'Environment :: Web Environment', - 'Framework :: CubicWeb', - 'Programming Language :: Python', - 'Programming Language :: JavaScript', -] - -# used packages -__depends__ = {'cubicweb': '>= 3.19.0', - 'cubicweb-file': '>= 1.9.0', - 'logilab-common': '>= 0.58.3', - } -__recommends__ = {'cubicweb-comment': None} - - -# packaging ### - -from os import listdir as _listdir -from os.path import join, isdir -from glob import glob - -THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname) - -def listdir(dirpath): - return [join(dirpath, fname) for fname in _listdir(dirpath) - if fname[0] != '.' and not fname.endswith('.pyc') - and not fname.endswith('~') - and not isdir(join(dirpath, fname))] - -data_files = [ - # common files - [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], - ] -# check for possible extended cube layout -for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration', 'wdoc'): - if isdir(dirname): - data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) -# Note: here, you'll need to add subdirectories if you want -# them to be included in the debian package diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubes/fakeemail/schema.py --- a/cubicweb/server/test/data-migractions/cubes/fakeemail/schema.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -"""entity/relation schemas to store email in an cubicweb instance - -:organization: Logilab -:copyright: 2006-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" - -from cubicweb import _ - -# pylint: disable-msg=E0611,F0401 -from yams.buildobjs import (SubjectRelation, RelationType, EntityType, - String, Datetime, Int, RelationDefinition) -from yams.reader import context - -from cubicweb.schema import ERQLExpression - - -class Email(EntityType): - """electronic mail""" - subject = String(fulltextindexed=True) - date = Datetime(description=_('UTC time on which the mail was sent')) - messageid = String(required=True, indexed=True) - headers = String(description=_('raw headers')) - - sender = SubjectRelation('EmailAddress', cardinality='?*') - # an email with only Bcc is acceptable, don't require any recipients - recipients = SubjectRelation('EmailAddress') - cc = SubjectRelation('EmailAddress') - - parts = SubjectRelation('EmailPart', cardinality='*1', composite='subject') - attachment = SubjectRelation('File') - - reply_to = SubjectRelation('Email', cardinality='?*') - cites = SubjectRelation('Email') - in_thread = SubjectRelation('EmailThread', cardinality='?*') - - -class EmailPart(EntityType): - """an email attachment""" - __permissions__ = { - 'read': ('managers', 'users', 'guests',), # XXX if E parts X, U has_read_permission E - 'add': ('managers', ERQLExpression('E parts X, U has_update_permission E'),), - 'delete': ('managers', ERQLExpression('E parts X, U has_update_permission E')), - 'update': ('managers', 'owners',), - } - - content = String(fulltextindexed=True) - content_format = String(required=True, maxsize=50) - ordernum = Int(required=True) - alternative = SubjectRelation('EmailPart', symmetric=True) - - -class EmailThread(EntityType): - """discussion thread""" - title = String(required=True, indexed=True, fulltextindexed=True) - see_also = SubjectRelation('EmailThread') - forked_from = SubjectRelation('EmailThread', cardinality='?*') - -class parts(RelationType): - """ """ - fulltext_container = 'subject' - -class sender(RelationType): - """ """ - inlined = True - -class in_thread(RelationType): - """ """ - inlined = True - -class reply_to(RelationType): - """ """ - inlined = True - -class generated_by(RelationType): - """mark an entity as generated from an email""" - cardinality = '?*' - subject = ('TrInfo',) - object = 'Email' - -# if comment is installed -if 'Comment' in context.defined: - class comment_generated_by(RelationDefinition): - subject = 'Comment' - name = 'generated_by' - object = 'Email' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_basket --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_basket Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_basket \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_card --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_card Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_card \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_comment --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_comment Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_comment \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,50 @@ +# pylint: disable-msg=W0622 +"""cubicweb-fakeemail packaging information""" + +modname = 'fakecustomtype' +distname = "cubicweb-%s" % modname + +numversion = (1, 0, 0) +version = '.'.join(str(num) for num in numversion) + +license = 'LGPL' +author = "Logilab" +author_email = "contact@logilab.fr" +web = 'http://www.cubicweb.org/project/%s' % distname +description = "whatever" +classifiers = [ + 'Environment :: Web Environment', + 'Framework :: CubicWeb', + 'Programming Language :: Python', + 'Programming Language :: JavaScript', +] + +# used packages +__depends__ = {'cubicweb': '>= 3.19.0', + } + + +# packaging ### + +from os import listdir as _listdir +from os.path import join, isdir +from glob import glob + +THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname) + +def listdir(dirpath): + return [join(dirpath, fname) for fname in _listdir(dirpath) + if fname[0] != '.' and not fname.endswith('.pyc') + and not fname.endswith('~') + and not isdir(join(dirpath, fname))] + +data_files = [ + # common files + [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], + ] +# check for possible extended cube layout +for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration', 'wdoc'): + if isdir(dirname): + data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) +# Note: here, you'll need to add subdirectories if you want +# them to be included in the debian package diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,7 @@ + +from yams.buildobjs import EntityType, make_type + +Numeric = make_type('Numeric') + +class Location(EntityType): + num = Numeric(scale=10, precision=18) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/site_cubicweb.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_fakecustomtype/site_cubicweb.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams import register_base_type +from logilab.database import get_db_helper +from logilab.database.sqlgen import SQLExpression + +_NUMERIC_PARAMETERS = {'scale': 0, 'precision': None} +register_base_type('Numeric', _NUMERIC_PARAMETERS) + +# Add the datatype to the helper mapping +pghelper = get_db_helper('postgres') + + +def pg_numeric_sqltype(rdef): + """Return a PostgreSQL column type corresponding to rdef + """ + return 'numeric(%s, %s)' % (rdef.precision, rdef.scale) + +pghelper.TYPE_MAPPING['Numeric'] = pg_numeric_sqltype diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakeemail/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakeemail/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_fakeemail/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,53 @@ +# pylint: disable-msg=W0622 +"""cubicweb-fakeemail packaging information""" + +modname = 'fakeemail' +distname = "cubicweb-%s" % modname + +numversion = (1, 10, 0) +version = '.'.join(str(num) for num in numversion) + +license = 'LGPL' +author = "Logilab" +author_email = "contact@logilab.fr" +web = 'http://www.cubicweb.org/project/%s' % distname +description = "email component for the CubicWeb framework" +classifiers = [ + 'Environment :: Web Environment', + 'Framework :: CubicWeb', + 'Programming Language :: Python', + 'Programming Language :: JavaScript', +] + +# used packages +__depends__ = {'cubicweb': '>= 3.19.0', + 'cubicweb-file': '>= 1.9.0', + 'logilab-common': '>= 0.58.3', + } +__recommends__ = {'cubicweb-comment': None} + + +# packaging ### + +from os import listdir as _listdir +from os.path import join, isdir +from glob import glob + +THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname) + +def listdir(dirpath): + return [join(dirpath, fname) for fname in _listdir(dirpath) + if fname[0] != '.' and not fname.endswith('.pyc') + and not fname.endswith('~') + and not isdir(join(dirpath, fname))] + +data_files = [ + # common files + [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], + ] +# check for possible extended cube layout +for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration', 'wdoc'): + if isdir(dirname): + data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) +# Note: here, you'll need to add subdirectories if you want +# them to be included in the debian package diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_fakeemail/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_fakeemail/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,86 @@ +"""entity/relation schemas to store email in an cubicweb instance + +:organization: Logilab +:copyright: 2006-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +from cubicweb import _ + +# pylint: disable-msg=E0611,F0401 +from yams.buildobjs import (SubjectRelation, RelationType, EntityType, + String, Datetime, Int, RelationDefinition) +from yams.reader import context + +from cubicweb.schema import ERQLExpression + + +class Email(EntityType): + """electronic mail""" + subject = String(fulltextindexed=True) + date = Datetime(description=_('UTC time on which the mail was sent')) + messageid = String(required=True, indexed=True) + headers = String(description=_('raw headers')) + + sender = SubjectRelation('EmailAddress', cardinality='?*') + # an email with only Bcc is acceptable, don't require any recipients + recipients = SubjectRelation('EmailAddress') + cc = SubjectRelation('EmailAddress') + + parts = SubjectRelation('EmailPart', cardinality='*1', composite='subject') + attachment = SubjectRelation('File') + + reply_to = SubjectRelation('Email', cardinality='?*') + cites = SubjectRelation('Email') + in_thread = SubjectRelation('EmailThread', cardinality='?*') + + +class EmailPart(EntityType): + """an email attachment""" + __permissions__ = { + 'read': ('managers', 'users', 'guests',), # XXX if E parts X, U has_read_permission E + 'add': ('managers', ERQLExpression('E parts X, U has_update_permission E'),), + 'delete': ('managers', ERQLExpression('E parts X, U has_update_permission E')), + 'update': ('managers', 'owners',), + } + + content = String(fulltextindexed=True) + content_format = String(required=True, maxsize=50) + ordernum = Int(required=True) + alternative = SubjectRelation('EmailPart', symmetric=True) + + +class EmailThread(EntityType): + """discussion thread""" + title = String(required=True, indexed=True, fulltextindexed=True) + see_also = SubjectRelation('EmailThread') + forked_from = SubjectRelation('EmailThread', cardinality='?*') + +class parts(RelationType): + """ """ + fulltext_container = 'subject' + +class sender(RelationType): + """ """ + inlined = True + +class in_thread(RelationType): + """ """ + inlined = True + +class reply_to(RelationType): + """ """ + inlined = True + +class generated_by(RelationType): + """mark an entity as generated from an email""" + cardinality = '?*' + subject = ('TrInfo',) + object = 'Email' + +# if comment is installed +if 'Comment' in context.defined: + class comment_generated_by(RelationDefinition): + subject = 'Comment' + name = 'generated_by' + object = 'Email' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_file --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_file Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_file \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_localperms --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_localperms Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_localperms \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/cubicweb_tag --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/cubicweb_tag Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_tag \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_basket --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_basket Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_basket \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_card --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_card Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_card \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_comment --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_comment Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_comment \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_file --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_file Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_file \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_localperms --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_localperms Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_localperms \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-migractions/migratedapp/cubicweb_tag --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data-migractions/migratedapp/cubicweb_tag Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../data/cubicweb_tag \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data-schema2sql/schema/Company.py --- a/cubicweb/server/test/data-schema2sql/schema/Company.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/data-schema2sql/schema/Company.py Fri May 24 16:29:14 2019 +0200 @@ -18,6 +18,8 @@ from yams.buildobjs import EntityType, RelationType, RelationDefinition, \ SubjectRelation, String +from cubicweb import _ + class Company(EntityType): name = String() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_basket/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_basket/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_basket/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_basket/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_basket/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,21 @@ +from yams.buildobjs import EntityType, RelationDefinition, String, RichString +from cubicweb.schema import ERQLExpression + + +class Basket(EntityType): + """a basket contains a set of other entities""" + __permissions__ = { + 'read': ('managers', ERQLExpression('X owned_by U'),), + 'add': ('managers', 'users',), + 'delete': ('managers', 'owners',), + 'update': ('managers', 'owners',), + } + + name = String(required=True, indexed=True, internationalizable=True, + maxsize=128) + description = RichString(fulltextindexed=True) + + +class in_basket(RelationDefinition): + subject = '*' + object = 'Basket' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_card/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_card/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_card/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_card/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_card/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams.buildobjs import EntityType, String, RichString + + +class Card(EntityType): + __permissions__ = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', 'users'), + 'delete': ('managers', 'owners'), + 'update': ('managers', 'owners',), + } + + title = String(required=True, fulltextindexed=True, maxsize=256) + synopsis = String(fulltextindexed=True, maxsize=512, + description=("an abstract for this card")) + content = RichString(fulltextindexed=True, internationalizable=True, + default_format='text/rest') + wikiid = String(maxsize=64, unique=True) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_comment/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_comment/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_comment/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_comment/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_comment/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,26 @@ +from yams.buildobjs import (EntityType, RelationType, SubjectRelation, + RichString) +from cubicweb.schema import RRQLExpression + + +class Comment(EntityType): + """a comment is a reply about another entity""" + __permissions__ = { + 'read': ('managers', 'users', 'guests',), + 'add': ('managers', 'users',), + 'delete': ('managers', 'owners',), + 'update': ('managers', 'owners',), + } + content = RichString(required=True, fulltextindexed=True) + comments = SubjectRelation('Comment', cardinality='1*', composite='object') + + +class comments(RelationType): + __permissions__ = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', 'users',), + 'delete': ('managers', RRQLExpression('S owned_by U'),), + } + inlined = True + composite = 'object' + cardinality = '1*' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_file/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_file/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_file/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_file/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_file/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,27 @@ +from yams.buildobjs import EntityType, String, Bytes, RichString + + +class File(EntityType): + """a downloadable file which may contains binary data""" + title = String(fulltextindexed=True, maxsize=256) + data = Bytes(required=True, description='file to upload') + data_format = String( + required=True, maxsize=128, + description=('MIME type of the file. Should be dynamically set ' + 'at upload time.')) + data_encoding = String( + maxsize=32, + description=('encoding of the file when it applies (e.g. text). ' + 'Should be dynamically set at upload time.')) + data_name = String( + required=True, fulltextindexed=True, + description=('name of the file. Should be dynamically set ' + 'at upload time.')) + data_hash = String( + maxsize=256, # max len of currently available hash alg + prefix is 140 + description=('hash of the file. May be set at upload time.'), + __permissions__={'read': ('managers', 'users', 'guests'), + 'add': (), + 'update': ()}) + description = RichString(fulltextindexed=True, internationalizable=True, + default_format='text/rest') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_localperms/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_localperms/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_localperms/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_localperms/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_localperms/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,42 @@ +from yams.buildobjs import EntityType, RelationType, RelationDefinition, String +from cubicweb.schema import PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS + + +class CWPermission(EntityType): + """entity type that may be used to construct some advanced security + configuration + """ + __permissions__ = PUB_SYSTEM_ENTITY_PERMS + + name = String(required=True, indexed=True, internationalizable=True, + maxsize=100, description=( + 'name or identifier of the permission')) + label = String(required=True, internationalizable=True, maxsize=100, + description=('distinct label to distinguate between other ' + 'permission entity of the same name')) + + +class granted_permission(RelationType): + """explicitly granted permission on an entity""" + __permissions__ = PUB_SYSTEM_REL_PERMS + # XXX cardinality = '*1' + + +class require_permission(RelationType): + __permissions__ = PUB_SYSTEM_REL_PERMS + + +class require_group(RelationDefinition): + """groups to which the permission is granted""" + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'CWPermission' + object = 'CWGroup' + + +class has_group_permission(RelationDefinition): + """short cut relation for 'U in_group G, P require_group G' for efficiency + reason. This relation is set automatically, you should not set this. + """ + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'CWUser' + object = 'CWPermission' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_tag/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_tag/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_tag/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/data/cubicweb_tag/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/server/test/data/cubicweb_tag/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams.buildobjs import EntityType, String, SubjectRelation, RelationType + + +class Tag(EntityType): + """tags are used by users to mark entities. + When you include the Tag entity, all application specific entities + may then be tagged using the "tags" relation. + """ + name = String(required=True, fulltextindexed=True, unique=True, + maxsize=128) + # when using this component, add the Tag tag X relation for each type that + # should be taggeable + tags = SubjectRelation('Tag', description="tagged objects") + + +class tags(RelationType): + """indicates that an entity is classified by a given tag""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_checkintegrity.py --- a/cubicweb/server/test/unittest_checkintegrity.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_checkintegrity.py Fri May 24 16:29:14 2019 +0200 @@ -16,15 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . +from io import StringIO import sys import unittest -from six import PY2 -if PY2: - from StringIO import StringIO -else: - from io import StringIO - from cubicweb import devtools # noqa: E402 from cubicweb.devtools.testlib import CubicWebTC # noqa: E402 from cubicweb.server.checkintegrity import check, check_indexes, reindex_entities # noqa: E402 diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_hook.py --- a/cubicweb/server/test/unittest_hook.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_hook.py Fri May 24 16:29:14 2019 +0200 @@ -30,10 +30,6 @@ class OperationsTC(CubicWebTC): - def setUp(self): - CubicWebTC.setUp(self) - self.hm = self.repo.hm - def test_late_operation(self): with self.admin_access.repo_cnx() as cnx: l1 = hook.LateOperation(cnx) @@ -63,9 +59,11 @@ pass -config = devtools.TestServerConfiguration('data', __file__) -config.bootstrap_cubes() -schema = config.load_schema() +def setUpModule(): + global config, schema + config = devtools.TestServerConfiguration('data', __file__) + config.bootstrap_cubes() + schema = config.load_schema() def tearDownModule(*args): global config, schema diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_ldapsource.py --- a/cubicweb/server/test/unittest_ldapsource.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_ldapsource.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ Those tests expect to have slapd, python-ldap3 and ldapscripts packages installed. """ -from __future__ import print_function - import os import sys import shutil @@ -31,9 +29,6 @@ import unittest from os.path import join -from six import string_types -from six.moves import range - from cubicweb import AuthenticationError, ValidationError from cubicweb.devtools.testlib import CubicWebTC from cubicweb.devtools.httptest import get_available_port @@ -180,7 +175,7 @@ """ modcmd = ['dn: %s' % dn, 'changetype: add'] for key, values in mods.items(): - if isinstance(values, string_types): + if isinstance(values, str): values = [values] for value in values: modcmd.append('%s: %s' % (key, value)) @@ -200,7 +195,7 @@ modcmd = ['dn: %s' % dn, 'changetype: modify'] for (kind, key), values in mods.items(): modcmd.append('%s: %s' % (kind, key)) - if isinstance(values, string_types): + if isinstance(values, str): values = [values] for value in values: modcmd.append('%s: %s' % (key, value)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_migractions.py --- a/cubicweb/server/test/unittest_migractions.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_migractions.py Fri May 24 16:29:14 2019 +0200 @@ -22,6 +22,7 @@ import sys from datetime import date from contextlib import contextmanager +from tempfile import TemporaryDirectory from logilab.common import tempattr @@ -30,7 +31,8 @@ from cubicweb import (ConfigurationError, ValidationError, ExecutionError, Binary) from cubicweb.devtools import startpgcluster, stoppgcluster -from cubicweb.devtools.testlib import CubicWebTC, TemporaryDirectory +from cubicweb.devtools.testlib import CubicWebTC +from cubicweb.schema import constraint_name_for from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.server.migractions import ServerMigrationHelper from cubicweb.server.sources import storages @@ -59,8 +61,6 @@ class MigrationConfig(cubicweb.devtools.TestServerConfiguration): default_sources = cubicweb.devtools.DEFAULT_PSQL_SOURCES - CUBES_PATH = cubicweb.devtools.TestServerConfiguration.CUBES_PATH + [ - osp.join(HERE, 'data-migractions', 'cubes')] class MigrationTC(CubicWebTC): @@ -94,10 +94,6 @@ global migrschema migrschema = config.load_schema() - def setUp(self): - self.configcls.cls_adjust_sys_path() - super(MigrationTC, self).setUp() - def tearDown(self): super(MigrationTC, self).tearDown() self.repo.vreg['etypes'].clear_caches() @@ -616,7 +612,7 @@ self.assertEqual(len(constraints), 1, constraints) rdef = migrschema['promo'].rdefs['Personne', 'String'] cstr = rdef.constraint_by_type('StaticVocabularyConstraint') - self.assertIn(cstr.name_for(rdef), constraints) + self.assertIn(constraint_name_for(cstr, rdef), constraints) def _erqlexpr_rset(self, cnx, action, ertype): rql = 'RQLExpression X WHERE ET is CWEType, ET %s_permission X, ET name %%(name)s' % action @@ -702,8 +698,8 @@ ['Bookmark', 'EmailThread', 'Folder', 'Note']) self.assertEqual(sorted(schema['see_also'].objects()), ['Bookmark', 'EmailThread', 'Folder', 'Note']) - from cubes.fakeemail.__pkginfo__ import version as email_version - from cubes.file.__pkginfo__ import version as file_version + from cubicweb_fakeemail.__pkginfo__ import version as email_version + from cubicweb_file.__pkginfo__ import version as file_version self.assertEqual( cnx.execute('Any V WHERE X value V, X pkey "system.version.fakeemail"')[0][0], email_version) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_postgres.py --- a/cubicweb/server/test/unittest_postgres.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_postgres.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ from datetime import datetime from threading import Thread -from six.moves import range - import logilab.database as lgdb from cubicweb import ValidationError from cubicweb.devtools import PostgresApptestConfiguration, startpgcluster, stoppgcluster diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_querier.py --- a/cubicweb/server/test/unittest_querier.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_querier.py Fri May 24 16:29:14 2019 +0200 @@ -25,8 +25,6 @@ import pytz -from six import PY2, integer_types, binary_type, text_type - from rql import BadRQLQuery from rql.utils import register_function, FunctionDescr @@ -134,8 +132,8 @@ def assertRQLEqual(self, expected, got): from rql import parse - self.assertMultiLineEqual(text_type(parse(expected)), - text_type(parse(got))) + self.assertMultiLineEqual(str(parse(expected)), + str(parse(got))) def test_preprocess_security(self): with self.user_groups_session('users') as cnx: @@ -176,7 +174,7 @@ 'ET': 'CWEType', 'ETN': 'String'}]) rql, solutions = partrqls[1] self.assertRQLEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, ' - 'X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWComputedRType, ' + 'X is IN(BaseTransition, Bookmark, CWAttribute, CWComputedRType, ' ' CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, ' ' CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, ' ' Comment, Division, Email, EmailPart, EmailThread, ExternalUri, File, Folder, ' @@ -188,7 +186,6 @@ {'X': 'Card', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Comment', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Division', 'ETN': 'String', 'ET': 'CWEType'}, - {'X': 'CWCache', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'CWComputedRType', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'CWConstraint', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'CWConstraintType', 'ETN': 'String', 'ET': 'CWEType'}, @@ -266,9 +263,6 @@ self.assertEqual(rset.description[0][0], 'Datetime') rset = self.qexecute('Any %(x)s', {'x': 1}) self.assertEqual(rset.description[0][0], 'Int') - if PY2: - rset = self.qexecute('Any %(x)s', {'x': long(1)}) - self.assertEqual(rset.description[0][0], 'Int') rset = self.qexecute('Any %(x)s', {'x': True}) self.assertEqual(rset.description[0][0], 'Boolean') rset = self.qexecute('Any %(x)s', {'x': 1.0}) @@ -328,7 +322,7 @@ def test_typed_eid(self): # should return an empty result set rset = self.qexecute('Any X WHERE X eid %(x)s', {'x': '1'}) - self.assertIsInstance(rset[0][0], integer_types) + self.assertIsInstance(rset[0][0], int) def test_bytes_storage(self): feid = self.qexecute('INSERT File X: X data_name "foo.pdf", ' @@ -615,16 +609,16 @@ self.assertListEqual(rset.rows, [[u'description_format', 13], [u'description', 14], - [u'name', 19], - [u'created_by', 45], - [u'creation_date', 45], - [u'cw_source', 45], - [u'cwuri', 45], - [u'in_basket', 45], - [u'is', 45], - [u'is_instance_of', 45], - [u'modification_date', 45], - [u'owned_by', 45]]) + [u'name', 18], + [u'created_by', 44], + [u'creation_date', 44], + [u'cw_source', 44], + [u'cwuri', 44], + [u'in_basket', 44], + [u'is', 44], + [u'is_instance_of', 44], + [u'modification_date', 44], + [u'owned_by', 44]]) def test_select_aggregat_having_dumb(self): # dumb but should not raise an error @@ -904,14 +898,14 @@ rset = self.qexecute('Any X, "toto" ORDERBY X WHERE X is CWGroup') self.assertEqual(rset.rows, [list(x) for x in zip((2,3,4,5), ('toto','toto','toto','toto',))]) - self.assertIsInstance(rset[0][1], text_type) + self.assertIsInstance(rset[0][1], str) self.assertEqual(rset.description, list(zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'), ('String', 'String', 'String', 'String',)))) rset = self.qexecute('Any X, %(value)s ORDERBY X WHERE X is CWGroup', {'value': 'toto'}) self.assertEqual(rset.rows, list(map(list, zip((2,3,4,5), ('toto','toto','toto','toto',))))) - self.assertIsInstance(rset[0][1], text_type) + self.assertIsInstance(rset[0][1], str) self.assertEqual(rset.description, list(zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'), ('String', 'String', 'String', 'String',)))) @@ -1074,10 +1068,10 @@ def test_insert_4ter(self): peid = self.qexecute("INSERT Personne X: X nom 'bidule'")[0][0] seid = self.qexecute("INSERT Societe Y: Y nom 'toto', X travaille Y WHERE X eid %(x)s", - {'x': text_type(peid)})[0][0] + {'x': str(peid)})[0][0] self.assertEqual(len(self.qexecute('Any X, Y WHERE X travaille Y')), 1) self.qexecute("INSERT Personne X: X nom 'chouette', X travaille Y WHERE Y eid %(x)s", - {'x': text_type(seid)}) + {'x': str(seid)}) self.assertEqual(len(self.qexecute('Any X, Y WHERE X travaille Y')), 2) def test_insert_5(self): @@ -1283,7 +1277,7 @@ rset = self.qexecute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto'") eid1, eid2 = rset[0][0], rset[0][1] self.qexecute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s", - {'x': text_type(eid1), 'y': text_type(eid2)}) + {'x': str(eid1), 'y': str(eid2)}) rset = self.qexecute('Any X, Y WHERE X travaille Y') self.assertEqual(len(rset.rows), 1) @@ -1333,7 +1327,7 @@ eid1, eid2 = rset[0][0], rset[0][1] rset = self.qexecute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s, " "NOT EXISTS(Z ecrit_par X)", - {'x': text_type(eid1), 'y': text_type(eid2)}) + {'x': str(eid1), 'y': str(eid2)}) self.assertEqual(tuplify(rset.rows), [(eid1, eid2)]) def test_update_query_error(self): @@ -1380,7 +1374,7 @@ cursor = cnx.cnxset.cu cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'" % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX)) - passwd = binary_type(cursor.fetchone()[0]) + passwd = bytes(cursor.fetchone()[0]) self.assertEqual(passwd, crypt_password('toto', passwd)) rset = self.qexecute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s", {'pwd': Binary(passwd)}) @@ -1397,7 +1391,7 @@ cursor = cnx.cnxset.cu cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'" % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX)) - passwd = binary_type(cursor.fetchone()[0]) + passwd = bytes(cursor.fetchone()[0]) self.assertEqual(passwd, crypt_password('tutu', passwd)) rset = cnx.execute("Any X WHERE X is CWUser, X login 'bob', X upassword %(pwd)s", {'pwd': Binary(passwd)}) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_repository.py --- a/cubicweb/server/test/unittest_repository.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_repository.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ import logging import unittest -from six.moves import range - from yams.constraints import UniqueConstraint from yams import register_base_type, unregister_base_type diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_rql2sql.py --- a/cubicweb/server/test/unittest_rql2sql.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_rql2sql.py Fri May 24 16:29:14 2019 +0200 @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """unit tests for module cubicweb.server.sources.rql2sql""" -from __future__ import print_function import sys import unittest diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_security.py --- a/cubicweb/server/test/unittest_security.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_security.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """functional tests for server'security""" -from six.moves import range - from logilab.common.testlib import unittest_main from cubicweb.devtools.testlib import CubicWebTC @@ -523,9 +521,8 @@ with self.new_access(u'iaminusersgrouponly').repo_cnx() as cnx: rqlst = self.repo.vreg.rqlhelper.parse('Any X WHERE X is_instance_of Societe') self.repo.vreg.solutions(cnx, rqlst, {}) - querier = cnx.repo.querier - querier._annotate(rqlst) - plan = querier.plan_factory(rqlst, {}, cnx) + self.repo.vreg.rqlhelper.annotate(rqlst) + plan = cnx.repo.querier.plan_factory(rqlst, {}, cnx) plan.preprocess(rqlst) self.assertEqual( rqlst.as_string(), diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_serverctl.py --- a/cubicweb/server/test/unittest_serverctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_serverctl.py Fri May 24 16:29:14 2019 +0200 @@ -1,7 +1,6 @@ import os.path as osp import shutil - -from mock import patch +from unittest.mock import patch from cubicweb import ExecutionError from cubicweb.devtools import testlib, ApptestConfiguration diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_storage.py --- a/cubicweb/server/test/unittest_storage.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_storage.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """unit tests for module cubicweb.server.sources.storages""" -from six import PY2 - from logilab.common.testlib import unittest_main, tag, Tags from cubicweb.devtools.testlib import CubicWebTC @@ -79,7 +77,7 @@ def fspath(self, cnx, entity): fspath = cnx.execute('Any fspath(D) WHERE F eid %(f)s, F data D', {'f': entity.eid})[0][0].getvalue() - return fspath if PY2 else fspath.decode('utf-8') + return fspath.decode('utf-8') def test_bfss_wrong_fspath_usage(self): with self.admin_access.repo_cnx() as cnx: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_undo.py --- a/cubicweb/server/test/unittest_undo.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_undo.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from six import text_type - from cubicweb import ValidationError from cubicweb.devtools.testlib import CubicWebTC from cubicweb.server.session import Connection @@ -254,7 +252,7 @@ "%s doesn't exist anymore." % g.eid]) with self.assertRaises(ValidationError) as cm: cnx.commit() - cm.exception.translate(text_type) + cm.exception.translate(str) self.assertEqual(cm.exception.entity, self.totoeid) self.assertEqual(cm.exception.errors, {'in_group-subject': u'at least one relation in_group is ' @@ -458,9 +456,9 @@ class UndoExceptionInUnicode(CubicWebTC): # problem occurs in string manipulation for python < 2.6 - def test___unicode__method(self): + def test___str__method(self): u = _UndoException(u"voilà") - self.assertIsInstance(text_type(u), text_type) + self.assertIsInstance(str(u), str) if __name__ == '__main__': diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/test/unittest_utils.py --- a/cubicweb/server/test/unittest_utils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/test/unittest_utils.py Fri May 24 16:29:14 2019 +0200 @@ -17,6 +17,8 @@ # with CubicWeb. If not, see . """Tests for cubicweb.server.utils module.""" +import sched + from cubicweb.devtools import testlib from cubicweb.server import utils @@ -40,7 +42,7 @@ self.assertEqual(utils.crypt_password('yyy', ''), '') def test_schedule_periodic_task(self): - scheduler = utils.scheduler() + scheduler = sched.scheduler() this = [] def fill_this(x): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/server/utils.py --- a/cubicweb/server/utils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/server/utils.py Fri May 24 16:29:14 2019 +0200 @@ -16,24 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """Some utilities for the CubicWeb server.""" -from __future__ import print_function - from functools import wraps -import sched -import sys import logging from threading import Thread from getpass import getpass -from six import PY2, text_type -from six.moves import input - from passlib.utils import handlers as uh, to_hash_str from passlib.context import CryptContext -from logilab.common.deprecation import deprecated - from cubicweb.md5crypt import crypt as md5crypt @@ -60,9 +51,7 @@ _CRYPTO_CTX = CryptContext(['sha512_crypt', CustomMD5Crypt, 'des_crypt', 'ldap_salted_sha1'], deprecated=['cubicwebmd5crypt', 'des_crypt']) -# for bw compat with passlib < 1.7 -if not hasattr(_CRYPTO_CTX, 'hash'): - _CRYPTO_CTX.hash = _CRYPTO_CTX.encrypt + verify_and_update = _CRYPTO_CTX.verify_and_update @@ -83,17 +72,6 @@ return b'' -@deprecated('[3.22] no more necessary, directly get eschema.eid') -def eschema_eid(cnx, eschema): - """get eid of the CWEType entity for the given yams type. - - This used to be necessary because when the schema has been loaded from the - file-system, not from the database, (e.g. during tests), eschema.eid was - not set. - """ - return eschema.eid - - DEFAULT_MSG = 'we need a manager connection on the repository \ (the server doesn\'t have to run, even should better not)' @@ -105,8 +83,6 @@ print(msg) while not user: user = input('login: ') - if PY2: - user = text_type(user, sys.stdin.encoding) passwd = getpass('%s: ' % passwdmsg) if confirm: while True: @@ -119,22 +95,6 @@ return user, passwd -if PY2: - import time # noqa - - class scheduler(sched.scheduler): - """Python2 version of sched.scheduler that matches Python3 API.""" - - def __init__(self, **kwargs): - kwargs.setdefault('timefunc', time.time) - kwargs.setdefault('delayfunc', time.sleep) - # sched.scheduler is an old-style class. - sched.scheduler.__init__(self, **kwargs) - -else: - scheduler = sched.scheduler - - def schedule_periodic_task(scheduler, interval, func, *args): """Enter a task with `func(*args)` as a periodic event in `scheduler` executing at `interval` seconds. Once executed, the task would re-schedule diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/skeleton/MANIFEST.in.tmpl --- a/cubicweb/skeleton/MANIFEST.in.tmpl Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/skeleton/MANIFEST.in.tmpl Fri May 24 16:29:14 2019 +0200 @@ -6,5 +6,5 @@ recursive-include cubicweb_%(cubename)s/wdoc * recursive-include test/data bootstrap_cubes *.py include *.ini -recursive-include debian changelog compat control copyright rules -include cubicweb-%(cubename)s.spec +prune debian +prune cubicweb-%(cubename)s.spec diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl --- a/cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl Fri May 24 16:29:14 2019 +0200 @@ -20,6 +20,6 @@ classifiers = [ 'Environment :: Web Environment', 'Framework :: CubicWeb', - 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Programming Language :: JavaScript', ] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/skeleton/tox.ini.tmpl --- a/cubicweb/skeleton/tox.ini.tmpl Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/skeleton/tox.ini.tmpl Fri May 24 16:29:14 2019 +0200 @@ -1,5 +1,5 @@ [tox] -envlist = py27,py3,flake8 +envlist = py3,flake8,check-manifest [testenv] deps = @@ -14,5 +14,12 @@ flake8 commands = flake8 +[testenv:check-manifest] +skip_install = true +deps = + check-manifest +commands = + {envpython} -m check_manifest {toxinidir} + [flake8] exclude = cubicweb_%(cubename)s/migration/*,test/data/*,.tox/* diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/sobjects/ldapparser.py --- a/cubicweb/sobjects/ldapparser.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/sobjects/ldapparser.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ unlike ldapuser source, this source is copy based and will import ldap content (beside passwords for authentication) into the system source. """ -from six.moves import map, filter - from logilab.common.decorators import cached, cachedproperty from logilab.common.shellutils import generate_password diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/sobjects/notification.py --- a/cubicweb/sobjects/notification.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/sobjects/notification.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ from itertools import repeat -from six import text_type - from logilab.common.textutils import normalize_text from logilab.common.registry import yes @@ -179,7 +177,7 @@ def context(self, **kwargs): entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) for key, val in kwargs.items(): - if val and isinstance(val, text_type) and val.strip(): + if val and isinstance(val, str) and val.strip(): kwargs[key] = self._cw._(val) kwargs.update({'user': self.user_data['login'], 'eid': entity.eid, @@ -252,7 +250,7 @@ def format_value(value): - if isinstance(value, text_type): + if isinstance(value, str): return u'"%s"' % value return value diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/sobjects/services.py --- a/cubicweb/sobjects/services.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/sobjects/services.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ import threading -from six import text_type - from cubicweb.server import Service from cubicweb.predicates import match_user_groups, match_kwargs @@ -111,7 +109,7 @@ def call(self, login, password, email=None, groups=None, **cwuserkwargs): cnx = self._cw - if isinstance(password, text_type): + if isinstance(password, str): # password should *always* be utf8 encoded password = password.encode('UTF8') cwuserkwargs['login'] = login diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/sobjects/test/data/cubicweb_card --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/sobjects/test/data/cubicweb_card Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../../../cubicweb/test/data/cubicweb_card \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/sobjects/test/data/cubicweb_comment --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/sobjects/test/data/cubicweb_comment Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../../../../cubicweb/test/data/cubicweb_comment \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/statsd_logger.py --- a/cubicweb/statsd_logger.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/statsd_logger.py Fri May 24 16:29:14 2019 +0200 @@ -58,6 +58,7 @@ import time import socket +from contextlib import contextmanager _bucket = 'cubicweb' _address = None @@ -87,19 +88,32 @@ _socket = socket.socket(family, socket.SOCK_DGRAM) +def teardown(): + """Unconfigure the statsd endpoint + + This is most likely only useful for unit tests""" + global _bucket, _address, _socket + _bucket = 'cubicweb' + _address = None + _socket = None + + def statsd_c(context, n=1): if _address is not None: - _socket.sendto('{0}.{1}:{2}|c\n'.format(_bucket, context, n), _address) + _socket.sendto('{0}.{1}:{2}|c\n'.format(_bucket, context, n).encode(), + _address) def statsd_g(context, value): if _address is not None: - _socket.sendto('{0}.{1}:{2}|g\n'.format(_bucket, context, value), _address) + _socket.sendto('{0}.{1}:{2}|g\n'.format(_bucket, context, value).encode(), + _address) def statsd_t(context, value): if _address is not None: - _socket.sendto('{0}.{1}:{2:.4f}|ms\n'.format(_bucket, context, value), _address) + _socket.sendto('{0}.{1}:{2:.4f}|ms\n'.format(_bucket, context, value).encode(), + _address) class statsd_timeit(object): @@ -125,7 +139,7 @@ finally: dt = 1000 * (time.time() - t0) msg = '{0}.{1}:{2:.4f}|ms\n{0}.{1}:1|c\n'.format( - _bucket, self.__name__, dt) + _bucket, self.__name__, dt).encode() _socket.sendto(msg, _address) def __get__(self, obj, objtype): @@ -134,3 +148,17 @@ return self import functools return functools.partial(self.__call__, obj) + + +@contextmanager +def statsd_timethis(ctxmsg): + if _address is not None: + t0 = time.time() + try: + yield + finally: + if _address is not None: + dt = 1000 * (time.time() - t0) + msg = '{0}.{1}:{2:.4f}|ms\n{0}.{1}:1|c\n'.format( + _bucket, ctxmsg, dt).encode() + _socket.sendto(msg, _address) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data-rewrite/cubicweb_card --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data-rewrite/cubicweb_card Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_card \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data-rewrite/cubicweb_localperms --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data-rewrite/cubicweb_localperms Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +../data/cubicweb_localperms \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_card/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_card/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_card/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_card/entities.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_card/entities.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,15 @@ +from cubicweb.entities import AnyEntity, fetch_config + + +class Card(AnyEntity): + __regid__ = 'Card' + rest_attr = 'wikiid' + + fetch_attrs, cw_fetch_order = fetch_config(['title']) + + def rest_path(self): + if self.wikiid: + return '%s/%s' % (str(self.e_schema).lower(), + self._cw.url_quote(self.wikiid, safe='/')) + else: + return super(Card, self).rest_path() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_card/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_card/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams.buildobjs import EntityType, String, RichString + + +class Card(EntityType): + __permissions__ = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', 'users'), + 'delete': ('managers', 'owners'), + 'update': ('managers', 'owners',), + } + + title = String(required=True, fulltextindexed=True, maxsize=256) + synopsis = String(fulltextindexed=True, maxsize=512, + description=("an abstract for this card")) + content = RichString(fulltextindexed=True, internationalizable=True, + default_format='text/rest') + wikiid = String(maxsize=64, unique=True) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_comment/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_comment/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_comment/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_comment/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,25 @@ +# pylint: disable=W0622 +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""cubicweb-comment packaging information""" + +distname = "cubicweb-comment" +modname = distname.split('-', 1)[1] + +numversion = (1, 4, 3) +version = '.'.join(str(num) for num in numversion) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_comment/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_comment/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,26 @@ +from yams.buildobjs import (EntityType, RelationType, SubjectRelation, + RichString) +from cubicweb.schema import RRQLExpression + + +class Comment(EntityType): + """a comment is a reply about another entity""" + __permissions__ = { + 'read': ('managers', 'users', 'guests',), + 'add': ('managers', 'users',), + 'delete': ('managers', 'owners',), + 'update': ('managers', 'owners',), + } + content = RichString(required=True, fulltextindexed=True) + comments = SubjectRelation('Comment', cardinality='1*', composite='object') + + +class comments(RelationType): + __permissions__ = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', 'users',), + 'delete': ('managers', RRQLExpression('S owned_by U'),), + } + inlined = True + composite = 'object' + cardinality = '1*' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_email/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_email/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_email/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_email/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,30 @@ +# pylint: disable=W0622 +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""cubicweb-email packaging information""" + +distname = "cubicweb-email" +modname = distname.split('-', 1)[1] + +numversion = (1, 4, 3) +version = '.'.join(str(num) for num in numversion) + + +__depends__ = {'cubicweb': None, + 'cubicweb-file': None} +__recommends__ = {'cubicweb-comment': None} diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_email/entities.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_email/entities.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_email/hooks.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_email/hooks.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_email/views/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_email/views/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_file/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_file/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_file/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_file/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,25 @@ +# pylint: disable=W0622 +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""cubicweb-file packaging information""" + +distname = "cubicweb-file" +modname = distname.split('-', 1)[1] + +numversion = (1, 4, 3) +version = '.'.join(str(num) for num in numversion) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_file/entities/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_file/entities/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_file/hooks/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_file/hooks/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_file/views.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_file/views.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_forge/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_forge/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_forge/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_forge/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,32 @@ +# pylint: disable=W0622 +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""cubicweb-forge packaging information""" + +distname = "cubicweb-forge" +modname = distname.split('-', 1)[1] + +numversion = (1, 4, 3) +version = '.'.join(str(num) for num in numversion) + + +__depends__ = {'cubicweb': None, + 'cubicweb-file': None, + 'cubicweb-email': None, + 'cubicweb-comment': None, + } diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_localperms/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_localperms/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_localperms/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_localperms/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_localperms/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,42 @@ +from yams.buildobjs import EntityType, RelationType, RelationDefinition, String +from cubicweb.schema import PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS + + +class CWPermission(EntityType): + """entity type that may be used to construct some advanced security + configuration + """ + __permissions__ = PUB_SYSTEM_ENTITY_PERMS + + name = String(required=True, indexed=True, internationalizable=True, + maxsize=100, description=( + 'name or identifier of the permission')) + label = String(required=True, internationalizable=True, maxsize=100, + description=('distinct label to distinguate between other ' + 'permission entity of the same name')) + + +class granted_permission(RelationType): + """explicitly granted permission on an entity""" + __permissions__ = PUB_SYSTEM_REL_PERMS + # XXX cardinality = '*1' + + +class require_permission(RelationType): + __permissions__ = PUB_SYSTEM_REL_PERMS + + +class require_group(RelationDefinition): + """groups to which the permission is granted""" + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'CWPermission' + object = 'CWGroup' + + +class has_group_permission(RelationDefinition): + """short cut relation for 'U in_group G, P require_group G' for efficiency + reason. This relation is set automatically, you should not set this. + """ + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'CWUser' + object = 'CWPermission' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_mycube/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_mycube/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,20 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""mycube's __init__ + +""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_mycube/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_mycube/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,22 @@ +# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +""" + +""" +distname = 'cubicweb-mycube' +version = '1.0.0' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_mycube/ccplugin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_mycube/ccplugin.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +# simply there to test ccplugin module autoloading diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_tag/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_tag/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_tag/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_tag/entities.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_tag/entities.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,6 @@ +from cubicweb.entities import AnyEntity, fetch_config + + +class Tag(AnyEntity): + __regid__ = 'Tag' + fetch_attrs, cw_fetch_order = fetch_config(['name']) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/cubicweb_tag/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/data/cubicweb_tag/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams.buildobjs import EntityType, String, SubjectRelation, RelationType + + +class Tag(EntityType): + """tags are used by users to mark entities. + When you include the Tag entity, all application specific entities + may then be tagged using the "tags" relation. + """ + name = String(required=True, fulltextindexed=True, unique=True, + maxsize=128) + # when using this component, add the Tag tag X relation for each type that + # should be taggeable + tags = SubjectRelation('Tag', description="tagged objects") + + +class tags(RelationType): + """indicates that an entity is classified by a given tag""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/legacy_cubes/comment --- a/cubicweb/test/data/legacy_cubes/comment Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_comment \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/legacy_cubes/email --- a/cubicweb/test/data/legacy_cubes/email Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_email/ \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/legacy_cubes/file --- a/cubicweb/test/data/legacy_cubes/file Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_file \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/legacy_cubes/forge --- a/cubicweb/test/data/legacy_cubes/forge Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_forge \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/legacy_cubes/mycube --- a/cubicweb/test/data/legacy_cubes/mycube Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -../libpython/cubicweb_mycube \ No newline at end of file diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_comment/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_comment/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_comment/__pkginfo__.py --- a/cubicweb/test/data/libpython/cubicweb_comment/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -# pylint: disable=W0622 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""cubicweb-comment packaging information""" - -distname = "cubicweb-comment" -modname = distname.split('-', 1)[1] - -numversion = (1, 4, 3) -version = '.'.join(str(num) for num in numversion) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_email/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_email/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_email/__pkginfo__.py --- a/cubicweb/test/data/libpython/cubicweb_email/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# pylint: disable=W0622 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""cubicweb-email packaging information""" - -distname = "cubicweb-email" -modname = distname.split('-', 1)[1] - -numversion = (1, 4, 3) -version = '.'.join(str(num) for num in numversion) - - -__depends__ = {'cubicweb': None, - 'cubicweb-file': None} -__recommends__ = {'cubicweb-comment': None} diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_email/entities.py --- a/cubicweb/test/data/libpython/cubicweb_email/entities.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_email/hooks.py --- a/cubicweb/test/data/libpython/cubicweb_email/hooks.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_email/views/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_email/views/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_file/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_file/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_file/__pkginfo__.py --- a/cubicweb/test/data/libpython/cubicweb_file/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -# pylint: disable=W0622 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""cubicweb-file packaging information""" - -distname = "cubicweb-file" -modname = distname.split('-', 1)[1] - -numversion = (1, 4, 3) -version = '.'.join(str(num) for num in numversion) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_file/entities/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_file/entities/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_file/hooks/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_file/hooks/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_file/views.py --- a/cubicweb/test/data/libpython/cubicweb_file/views.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -"test" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_forge/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_forge/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_forge/__pkginfo__.py --- a/cubicweb/test/data/libpython/cubicweb_forge/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -# pylint: disable=W0622 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""cubicweb-forge packaging information""" - -distname = "cubicweb-forge" -modname = distname.split('-', 1)[1] - -numversion = (1, 4, 3) -version = '.'.join(str(num) for num in numversion) - - -__depends__ = {'cubicweb': None, - 'cubicweb-file': None, - 'cubicweb-email': None, - 'cubicweb-comment': None, - } diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_mycube/__init__.py --- a/cubicweb/test/data/libpython/cubicweb_mycube/__init__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""mycube's __init__ - -""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_mycube/__pkginfo__.py --- a/cubicweb/test/data/libpython/cubicweb_mycube/__pkginfo__.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -""" - -""" -distname = 'cubicweb-mycube' -version = '1.0.0' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/data/libpython/cubicweb_mycube/ccplugin.py --- a/cubicweb/test/data/libpython/cubicweb_mycube/ccplugin.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -# simply there to test ccplugin module autoloading diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_binary.py --- a/cubicweb/test/unittest_binary.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_binary.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ import os.path as osp import pickle -from six import PY2 - from logilab.common.shellutils import tempdir from cubicweb import Binary @@ -32,10 +30,7 @@ Binary() Binary(b'toto') Binary(bytearray(b'toto')) - if PY2: - Binary(buffer('toto')) # noqa: F821 - else: - Binary(memoryview(b'toto')) + Binary(memoryview(b'toto')) with self.assertRaises((AssertionError, TypeError)): # TypeError is raised by BytesIO if python runs with -O Binary(u'toto') @@ -44,10 +39,7 @@ b = Binary() b.write(b'toto') b.write(bytearray(b'toto')) - if PY2: - b.write(buffer('toto')) # noqa: F821 - else: - b.write(memoryview(b'toto')) + b.write(memoryview(b'toto')) with self.assertRaises((AssertionError, TypeError)): # TypeError is raised by BytesIO if python runs with -O b.write(u'toto') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_cubes.py --- a/cubicweb/test/unittest_cubes.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with CubicWeb. If not, see . -"""Unit tests for "cubes" importer.""" - -from contextlib import contextmanager -import os -from os import path -import sys - -from six import PY2 - -from cubicweb import _CubesImporter -from cubicweb.cwconfig import CubicWebConfiguration -from cubicweb.devtools.testlib import TemporaryDirectory, TestCase - - -@contextmanager -def temp_cube(): - with TemporaryDirectory() as tempdir: - try: - libdir = path.join(tempdir, 'libpython') - cubedir = path.join(libdir, 'cubicweb_foo') - os.makedirs(cubedir) - check_code = ("import logging\n" - "logging.getLogger('cubicweb_foo')" - ".warn('imported %s', __name__)\n") - with open(path.join(cubedir, '__init__.py'), 'w') as f: - f.write("'cubicweb_foo application package'\n" + check_code) - with open(path.join(cubedir, 'bar.py'), 'w') as f: - f.write(check_code + 'baz = 1\n') - sys.path.append(libdir) - yield cubedir - finally: - sys.path.remove(libdir) - - -class CubesImporterTC(TestCase): - - def setUp(self): - # During discovery, CubicWebConfiguration.cls_adjust_sys_path may be - # called (probably because of cubicweb.devtools's __init__.py), so - # uninstall _CubesImporter. - for x in sys.meta_path: - if isinstance(x, _CubesImporter): - sys.meta_path.remove(x) - # Keep track of initial sys.path and sys.meta_path. - self.orig_sys_path = sys.path[:] - self.orig_sys_meta_path = sys.meta_path[:] - - def tearDown(self): - # Cleanup any imported "cubes". - for name in list(sys.modules): - if name.startswith('cubes') or name.startswith('cubicweb_'): - del sys.modules[name] - # Restore sys.{meta_,}path - sys.path[:] = self.orig_sys_path - sys.meta_path[:] = self.orig_sys_meta_path - - def test_importer_install(self): - _CubesImporter.install() - self.assertIsInstance(sys.meta_path[-1], _CubesImporter) - - def test_config_installs_importer(self): - CubicWebConfiguration.cls_adjust_sys_path() - self.assertIsInstance(sys.meta_path[-1], _CubesImporter) - - def test_import_cube_as_package_legacy_name(self): - """Check for import of an actual package-cube using legacy name""" - with temp_cube() as cubedir: - import cubicweb_foo # noqa - del sys.modules['cubicweb_foo'] - with self.assertRaises(ImportError): - import cubes.foo - CubicWebConfiguration.cls_adjust_sys_path() - import cubes.foo # noqa - self.assertEqual(cubes.foo.__path__, [cubedir]) - self.assertEqual(cubes.foo.__doc__, - 'cubicweb_foo application package') - # Import a submodule. - from cubes.foo import bar - self.assertEqual(bar.baz, 1) - - def test_reload_cube(self): - """reloading cubes twice should return the same module""" - CubicWebConfiguration.cls_adjust_sys_path() - import cubes - if PY2: - new = reload(cubes) - else: - import importlib - new = importlib.reload(cubes) - self.assertIs(new, cubes) - - def test_no_double_import(self): - """Check new and legacy import the same module once""" - with temp_cube(): - CubicWebConfiguration.cls_adjust_sys_path() - with self.assertLogs('cubicweb_foo', 'WARNING') as cm: - from cubes.foo import bar - from cubicweb_foo import bar as bar2 - self.assertIs(bar, bar2) - self.assertIs(sys.modules['cubes.foo'], - sys.modules['cubicweb_foo']) - self.assertEqual(cm.output, [ - 'WARNING:cubicweb_foo:imported cubicweb_foo', - # module __name__ for subpackage differ along python version - # for PY2 it's based on how the module was imported "from - # cubes.foo import bar" and for PY3 based on __name__ of parent - # module "cubicweb_foo". Not sure if it's an issue, but PY3 - # behavior looks better. - 'WARNING:cubicweb_foo:imported ' + ( - 'cubes.foo.bar' if PY2 else 'cubicweb_foo.bar') - ]) - - def test_import_legacy_cube(self): - """Check that importing a legacy cube works when sys.path got adjusted. - """ - CubicWebConfiguration.cls_adjust_sys_path() - import cubes.card # noqa - - def test_import_cube_as_package_after_legacy_cube(self): - """Check import of a "cube as package" after a legacy cube.""" - CubicWebConfiguration.cls_adjust_sys_path() - with temp_cube() as cubedir: - import cubes.card - import cubes.foo - self.assertEqual(cubes.foo.__path__, [cubedir]) - - def test_cube_inexistant(self): - """Check for import of an inexistant cube""" - CubicWebConfiguration.cls_adjust_sys_path() - with self.assertRaises(ImportError) as cm: - import cubes.doesnotexists # noqa - msg = "No module named " + ("doesnotexists" if PY2 else "'cubes.doesnotexists'") - self.assertEqual(str(cm.exception), msg) - - -if __name__ == '__main__': - import unittest - unittest.main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_cwconfig.py --- a/cubicweb/test/unittest_cwconfig.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_cwconfig.py Fri May 24 16:29:14 2019 +0200 @@ -18,22 +18,20 @@ """cubicweb.cwconfig unit tests""" import contextlib -import compileall import functools import sys import os import pkgutil from os.path import dirname, join from pkg_resources import EntryPoint, Distribution +from tempfile import TemporaryDirectory import unittest - -from mock import patch -from six import PY3 +from unittest.mock import patch from logilab.common.modutils import cleanup_sys_modules from cubicweb.devtools import ApptestConfiguration -from cubicweb.devtools.testlib import BaseTestCase, TemporaryDirectory +from cubicweb.devtools.testlib import BaseTestCase from cubicweb.cwconfig import ( CubicWebConfiguration, _expand_modname) @@ -61,33 +59,31 @@ @contextlib.contextmanager -def temp_config(appid, instance_dir, cubes_dir, cubes): +def temp_config(appid, instance_dir, cubes): """context manager that create a config object with specified appid, - instance_dir, cubes_dir and cubes""" + instance_dir and cubes""" cls = CubicWebConfiguration - old = (cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH, + old = (cls._INSTANCES_DIR, sys.path[:], sys.meta_path[:]) old_modules = set(sys.modules) try: - cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH = ( - instance_dir, cubes_dir, []) + cls._INSTANCES_DIR = instance_dir config = cls(appid) config._cubes = cubes config.adjust_sys_path() yield config finally: - (cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH, - sys.path[:], sys.meta_path[:]) = old + (cls._INSTANCES_DIR, sys.path[:], sys.meta_path[:]) = old for module in set(sys.modules) - old_modules: del sys.modules[module] def iter_entry_points(group, name): """Mock pkg_resources.iter_entry_points to yield EntryPoint from - packages found in test/data/libpython even though these are not + packages found in test/data even though these are not installed. """ - libpython = CubicWebConfigurationTC.datapath('libpython') + libpython = CubicWebConfigurationTC.datapath() prefix = 'cubicweb_' for pkgname in os.listdir(libpython): if not pkgname.startswith(prefix): @@ -101,19 +97,18 @@ @classmethod def setUpClass(cls): - sys.path.append(cls.datapath('libpython')) + sys.path.append(cls.datapath()) @classmethod def tearDownClass(cls): - sys.path.remove(cls.datapath('libpython')) + sys.path.remove(cls.datapath()) def setUp(self): self.config = ApptestConfiguration('data', __file__) self.config._cubes = ('email', 'file') def tearDown(self): - ApptestConfiguration.CUBES_PATH = [] - cleanup_sys_modules([self.datapath('libpython')]) + cleanup_sys_modules([self.datapath()]) def test_migration_scripts_dir(self): mscripts = os.listdir(self.config.migration_scripts_dir()) @@ -124,17 +119,19 @@ @patch('pkg_resources.iter_entry_points', side_effect=iter_entry_points) def test_available_cubes(self, mock_iter_entry_points): expected_cubes = [ - 'card', 'comment', 'cubicweb_comment', 'cubicweb_email', 'file', - 'cubicweb_file', 'cubicweb_forge', 'localperms', - 'cubicweb_mycube', 'tag', + 'cubicweb_card', + 'cubicweb_comment', + 'cubicweb_email', + 'cubicweb_file', + 'cubicweb_forge', + 'cubicweb_localperms', + 'cubicweb_mycube', + 'cubicweb_tag', ] - self._test_available_cubes(expected_cubes) + self.assertEqual(self.config.available_cubes(), expected_cubes) mock_iter_entry_points.assert_called_once_with( group='cubicweb.cubes', name=None) - def _test_available_cubes(self, expected_cubes): - self.assertEqual(self.config.available_cubes(), expected_cubes) - def test_reorder_cubes(self): # forge depends on email and file and comment # email depends on file @@ -187,71 +184,8 @@ self.config.load_cwctl_plugins() mock_iter_entry_points.assert_called_once_with( group='cubicweb.cubes', name=None) - self.assertNotIn('cubes.mycube.ccplugin', sys.modules, sorted(sys.modules)) self.assertIn('cubicweb_mycube.ccplugin', sys.modules, sorted(sys.modules)) - -class CubicWebConfigurationWithLegacyCubesTC(CubicWebConfigurationTC): - - @classmethod - def setUpClass(cls): - pass - - @classmethod - def tearDownClass(cls): - pass - - def setUp(self): - self.custom_cubes_dir = self.datapath('legacy_cubes') - cleanup_sys_modules([self.custom_cubes_dir, ApptestConfiguration.CUBES_DIR]) - super(CubicWebConfigurationWithLegacyCubesTC, self).setUp() - self.config.__class__.CUBES_PATH = [self.custom_cubes_dir] - self.config.adjust_sys_path() - - def tearDown(self): - ApptestConfiguration.CUBES_PATH = [] - - def test_available_cubes(self): - expected_cubes = sorted(set([ - # local cubes - 'comment', 'email', 'file', 'forge', 'mycube', - # test dependencies - 'card', 'file', 'localperms', 'tag', - ])) - self._test_available_cubes(expected_cubes) - - def test_reorder_cubes_recommends(self): - from cubes.comment import __pkginfo__ as comment_pkginfo - self._test_reorder_cubes_recommends(comment_pkginfo) - - def test_cubes_path(self): - # make sure we don't import the email cube, but the stdlib email package - import email - self.assertNotEqual(dirname(email.__file__), self.config.CUBES_DIR) - self.config.__class__.CUBES_PATH = [self.custom_cubes_dir] - self.assertEqual(self.config.cubes_search_path(), - [self.custom_cubes_dir, self.config.CUBES_DIR]) - self.config.__class__.CUBES_PATH = [self.custom_cubes_dir, - self.config.CUBES_DIR, 'unexistant'] - # filter out unexistant and duplicates - self.assertEqual(self.config.cubes_search_path(), - [self.custom_cubes_dir, - self.config.CUBES_DIR]) - self.assertIn('mycube', self.config.available_cubes()) - # test cubes python path - self.config.adjust_sys_path() - import cubes - self.assertEqual(cubes.__path__, self.config.cubes_search_path()) - # this import should succeed once path is adjusted - from cubes import mycube - self.assertEqual(mycube.__path__, [join(self.custom_cubes_dir, 'mycube')]) - # file cube should be overriden by the one found in data/cubes - sys.modules.pop('cubes.file') - if hasattr(cubes, 'file'): - del cubes.file - from cubes import file - self.assertEqual(file.__path__, [join(self.custom_cubes_dir, 'file')]) - def test_config_value_from_environment_str(self): self.assertIsNone(self.config['base-url']) os.environ['CW_BASE_URL'] = 'https://www.cubicweb.org' @@ -279,11 +213,6 @@ finally: del os.environ['CW_ALLOW_EMAIL_LOGIN'] - def test_ccplugin_modname(self): - self.config.load_cwctl_plugins() - self.assertIn('cubes.mycube.ccplugin', sys.modules, sorted(sys.modules)) - self.assertNotIn('cubicweb_mycube.ccplugin', sys.modules, sorted(sys.modules)) - class ModnamesTC(unittest.TestCase): @@ -337,13 +266,6 @@ join(tempdir, 'b', 'c.py'), join(tempdir, 'b', 'd', '__init__.py'), ): - if not PY3: - # ensure pyc file exists. - # Doesn't required for PY3 since it create __pycache__ - # directory and will not import if source file doesn't - # exists. - compileall.compile_file(source, force=True) - self.assertTrue(os.path.exists(source + 'c')) # remove source file os.remove(source) self.assertEqual(list(_expand_modname('lib.c')), []) @@ -368,9 +290,6 @@ join(libdir, 'cubicweb_foo', 'schema', 'b.py'), # subpackages should not be loaded join(libdir, 'cubicweb_foo', 'schema', 'c', '__init__.py'), - join(libdir, 'cubes', '__init__.py'), - join(libdir, 'cubes', 'bar', '__init__.py'), - join(libdir, 'cubes', 'bar', 'schema.py'), join(libdir, '_instance_dir', 'data1', 'schema.py'), join(libdir, '_instance_dir', 'data2', 'noschema.py'), ): @@ -380,24 +299,20 @@ ('cubicweb', 'cubicweb.schemas.base'), ('cubicweb', 'cubicweb.schemas.workflow'), ('cubicweb', 'cubicweb.schemas.Bookmark'), - ('bar', 'cubes.bar.schema'), ('foo', 'cubicweb_foo.schema'), ('foo', 'cubicweb_foo.schema.a'), ('foo', 'cubicweb_foo.schema.b'), ] # app has schema file - instance_dir, cubes_dir = ( - join(libdir, '_instance_dir'), join(libdir, 'cubes')) - with temp_config('data1', instance_dir, cubes_dir, - ('foo', 'bar')) as config: + instance_dir = join(libdir, '_instance_dir') + with temp_config('data1', instance_dir, ('foo',)) as config: self.assertEqual(pkgutil.find_loader('schema').get_filename(), join(libdir, '_instance_dir', 'data1', 'schema.py')) self.assertEqual(config.schema_modnames(), expected + [('data', 'schema')]) # app doesn't have schema file - with temp_config('data2', instance_dir, cubes_dir, - ('foo', 'bar')) as config: + with temp_config('data2', instance_dir, ('foo',)) as config: self.assertEqual(pkgutil.find_loader('schema').get_filename(), join(libdir, 'schema.py')) self.assertEqual(config.schema_modnames(), expected) @@ -414,15 +329,11 @@ join(libdir, 'cubicweb_foo', 'entities', 'b', 'a.py'), join(libdir, 'cubicweb_foo', 'entities', 'b', 'c', '__init__.py'), join(libdir, 'cubicweb_foo', 'hooks.py'), - join(libdir, 'cubes', '__init__.py'), - join(libdir, 'cubes', 'bar', '__init__.py'), - join(libdir, 'cubes', 'bar', 'hooks.py'), join(libdir, '_instance_dir', 'data1', 'entities.py'), join(libdir, '_instance_dir', 'data2', 'hooks.py'), ): create_filepath(filepath) - instance_dir, cubes_dir = ( - join(libdir, '_instance_dir'), join(libdir, 'cubes')) + instance_dir = join(libdir, '_instance_dir') expected = [ 'cubicweb.entities', 'cubicweb.entities.adapters', @@ -431,7 +342,6 @@ 'cubicweb.entities.schemaobjs', 'cubicweb.entities.sources', 'cubicweb.entities.wfobjs', - 'cubes.bar.hooks', 'cubicweb_foo.entities', 'cubicweb_foo.entities.a', 'cubicweb_foo.entities.b', @@ -440,14 +350,12 @@ 'cubicweb_foo.hooks', ] # data1 has entities - with temp_config('data1', instance_dir, cubes_dir, - ('foo', 'bar')) as config: + with temp_config('data1', instance_dir, ('foo',)) as config: config.cube_appobject_path = set(['entities', 'hooks']) self.assertEqual(config.appobjects_modnames(), expected + ['entities']) # data2 has hooks - with temp_config('data2', instance_dir, cubes_dir, - ('foo', 'bar')) as config: + with temp_config('data2', instance_dir, ('foo',)) as config: config.cube_appobject_path = set(['entities', 'hooks']) self.assertEqual(config.appobjects_modnames(), expected + ['hooks']) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_cwctl.py --- a/cubicweb/test/unittest_cwctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_cwctl.py Fri May 24 16:29:14 2019 +0200 @@ -18,12 +18,9 @@ import sys import os from os.path import join -from io import StringIO, BytesIO +from io import StringIO import unittest - -from six import PY2 - -from mock import patch +from unittest.mock import patch from cubicweb.cwctl import ListCommand from cubicweb.devtools.testlib import CubicWebTC @@ -38,7 +35,7 @@ tearDownClass = unittest_cwconfig.CubicWebConfigurationTC.tearDownClass def setUp(self): - self.stream = BytesIO() if PY2 else StringIO() + self.stream = StringIO() sys.stdout = self.stream def tearDown(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_entity.py --- a/cubicweb/test/unittest_entity.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_entity.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from datetime import datetime -from six import text_type - from logilab.common import tempattr from logilab.common.decorators import clear_cache @@ -800,11 +798,11 @@ # ambiguity test person2 = req.create_entity('Personne', prenom=u'remi', nom=u'doe') person.cw_clear_all_caches() - self.assertEqual(person.rest_path(), text_type(person.eid)) - self.assertEqual(person2.rest_path(), text_type(person2.eid)) + self.assertEqual(person.rest_path(), str(person.eid)) + self.assertEqual(person2.rest_path(), str(person2.eid)) # unique attr with None value (nom in this case) friend = req.create_entity('Ami', prenom=u'bob') - self.assertEqual(friend.rest_path(), text_type(friend.eid)) + self.assertEqual(friend.rest_path(), str(friend.eid)) # 'ref' below is created without the unique but not required # attribute, make sur that the unique _and_ required 'ean' is used # as the rest attribute @@ -837,16 +835,6 @@ note.cw_set(ecrit_par=person.eid) self.assertEqual(len(person.reverse_ecrit_par), 2) - def test_metainformation(self): - with self.admin_access.client_cnx() as cnx: - note = cnx.create_entity('Note', type=u'z') - cnx.commit() - note.cw_clear_all_caches() - metainf = note.cw_metainformation() - self.assertEqual(metainf, {'type': u'Note', - 'extid': None, - 'source': {'uri': 'system'}}) - def test_absolute_url_empty_field(self): with self.admin_access.web_request() as req: card = req.create_entity('Card', wikiid=u'', title=u'test') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_predicates.py --- a/cubicweb/test/unittest_predicates.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_predicates.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from operator import eq, lt, le, gt from contextlib import contextmanager -from six.moves import range - from logilab.common.testlib import TestCase, unittest_main from logilab.common.decorators import clear_cache @@ -31,12 +29,10 @@ multi_lines_rset, score_entity, is_in_state, rql_condition, relation_possible, match_form_params, paginated_rset) -from cubicweb.selectors import on_transition # XXX on_transition is deprecated from cubicweb.view import EntityAdapter from cubicweb.web import action - class ImplementsTC(CubicWebTC): def test_etype_priority(self): with self.admin_access.web_request() as req: @@ -147,65 +143,6 @@ self.assertEqual(str(cm.exception), "wf_test: unknown state(s): unknown,weird") - def test_on_transition(self): - with self.statefull_stuff() as (req, wf_entity, rset, adapter): - for transition in ('validate', 'forsake'): - selector = on_transition(transition) - self.assertEqual(selector(None, req, rset=rset), 0) - - adapter.fire_transition('validate') - req.cnx.commit(); wf_entity.cw_clear_all_caches() - self.assertEqual(adapter.state, 'validated') - - clear_cache(rset, 'get_entity') - - selector = on_transition("validate") - self.assertEqual(selector(None, req, rset=rset), 1) - selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, req, rset=rset), 1) - selector = on_transition("forsake") - self.assertEqual(selector(None, req, rset=rset), 0) - - adapter.fire_transition('forsake') - req.cnx.commit(); wf_entity.cw_clear_all_caches() - self.assertEqual(adapter.state, 'abandoned') - - clear_cache(rset, 'get_entity') - - selector = on_transition("validate") - self.assertEqual(selector(None, req, rset=rset), 0) - selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, req, rset=rset), 1) - selector = on_transition("forsake") - self.assertEqual(selector(None, req, rset=rset), 1) - - def test_on_transition_unvalid_names(self): - with self.statefull_stuff() as (req, wf_entity, rset, adapter): - selector = on_transition("unknown") - with self.assertRaises(ValueError) as cm: - selector(None, req, rset=rset) - self.assertEqual(str(cm.exception), - "wf_test: unknown transition(s): unknown") - selector = on_transition("weird", "unknown", "validate", "weird") - with self.assertRaises(ValueError) as cm: - selector(None, req, rset=rset) - self.assertEqual(str(cm.exception), - "wf_test: unknown transition(s): unknown,weird") - - def test_on_transition_with_no_effect(self): - """selector will not be triggered with `change_state()`""" - with self.statefull_stuff() as (req, wf_entity, rset, adapter): - adapter.change_state('validated') - req.cnx.commit(); wf_entity.cw_clear_all_caches() - self.assertEqual(adapter.state, 'validated') - - selector = on_transition("validate") - self.assertEqual(selector(None, req, rset=rset), 0) - selector = on_transition("validate", "forsake") - self.assertEqual(selector(None, req, rset=rset), 0) - selector = on_transition("forsake") - self.assertEqual(selector(None, req, rset=rset), 0) - class RelationPossibleTC(CubicWebTC): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_req.py --- a/cubicweb/test/unittest_req.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_req.py Fri May 24 16:29:14 2019 +0200 @@ -18,7 +18,7 @@ from logilab.common.testlib import TestCase, unittest_main from cubicweb import ObjectNotFound -from cubicweb.req import RequestSessionBase, FindEntityError +from cubicweb.req import RequestSessionBase from cubicweb.devtools.testlib import CubicWebTC from cubicweb import Unauthorized @@ -62,64 +62,12 @@ with self.admin_access.repo_cnx() as session: self.assertEqual(session.base_url(), base_url) - def test_secure_deprecated(self): - with self.admin_access.cnx() as cnx: - with self.assertWarns(DeprecationWarning): - cnx.base_url(secure=True) - with self.assertRaises(TypeError): - cnx.base_url(thing=42) - with self.assertWarns(DeprecationWarning): - cnx.build_url('ah', __secure__='whatever') - def test_view_catch_ex(self): with self.admin_access.web_request() as req: rset = req.execute('CWUser X WHERE X login "hop"') self.assertEqual(req.view('oneline', rset, 'null'), '') self.assertRaises(ObjectNotFound, req.view, 'onelinee', rset, 'null') - def test_find_one_entity(self): - with self.admin_access.web_request() as req: - req.create_entity( - 'CWUser', login=u'cdevienne', upassword=u'cdevienne', - surname=u'de Vienne', firstname=u'Christophe', - in_group=req.find('CWGroup', name=u'users').one()) - - req.create_entity( - 'CWUser', login=u'adim', upassword='adim', surname=u'di mascio', - firstname=u'adrien', - in_group=req.find('CWGroup', name=u'users').one()) - - u = req.find_one_entity('CWUser', login=u'cdevienne') - self.assertEqual(u.firstname, u"Christophe") - - with self.assertRaises(FindEntityError): - req.find_one_entity('CWUser', login=u'patanok') - - with self.assertRaises(FindEntityError): - req.find_one_entity('CWUser') - - def test_find_entities(self): - with self.admin_access.web_request() as req: - req.create_entity( - 'CWUser', login=u'cdevienne', upassword=u'cdevienne', - surname=u'de Vienne', firstname=u'Christophe', - in_group=req.find('CWGroup', name=u'users').one()) - - req.create_entity( - 'CWUser', login=u'adim', upassword='adim', surname=u'di mascio', - firstname=u'adrien', - in_group=req.find('CWGroup', name=u'users').one()) - - users = list(req.find_entities('CWUser', login=u'cdevienne')) - self.assertEqual(1, len(users)) - self.assertEqual(users[0].firstname, u"Christophe") - - users = list(req.find_entities('CWUser', login=u'patanok')) - self.assertEqual(0, len(users)) - - users = list(req.find_entities('CWUser')) - self.assertEqual(4, len(users)) - def test_find(self): with self.admin_access.web_request() as req: req.create_entity( @@ -153,17 +101,17 @@ users = list(rset.entities()) self.assertEqual(len(users), 2) - with self.assertRaisesRegexp( + with self.assertRaisesRegex( KeyError, "^'chapeau not in CWUser subject relations'$" ): req.find('CWUser', chapeau=u"melon") - with self.assertRaisesRegexp( + with self.assertRaisesRegex( KeyError, "^'buddy not in CWUser object relations'$" ): req.find('CWUser', reverse_buddy=users[0]) - with self.assertRaisesRegexp( + with self.assertRaisesRegex( NotImplementedError, '^in_group: list of values are not supported$' ): req.find('CWUser', in_group=[1, 2]) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_rqlrewrite.py --- a/cubicweb/test/unittest_rqlrewrite.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_rqlrewrite.py Fri May 24 16:29:14 2019 +0200 @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from six import string_types - from logilab.common.testlib import mock_object from logilab.common.decorators import monkeypatch from yams import BadSchemaDefinition @@ -88,7 +86,7 @@ snippet_varmap[snippet].update(varmap) continue snippet_varmap[snippet] = varmap - if isinstance(snippet, string_types): + if isinstance(snippet, str): snippet = mock_object(snippet_rqlst=parse(u'Any X WHERE ' + snippet).children[0], expression=u'Any X WHERE ' + snippet) rqlexprs.append(snippet) @@ -602,12 +600,11 @@ def process(self, rql, args=None): if args is None: args = {} - querier = self.repo.querier union = parse(rql) # self.vreg.parse(rql, annotate=True) with self.admin_access.repo_cnx() as cnx: self.vreg.solutions(cnx, union, args) - querier._annotate(union) - plan = querier.plan_factory(union, args, cnx) + self.vreg.rqlhelper.annotate(union) + plan = self.repo.querier.plan_factory(union, args, cnx) plan.preprocess(union) return union diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_rset.py --- a/cubicweb/test/unittest_rset.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_rset.py Fri May 24 16:29:14 2019 +0200 @@ -1,5 +1,5 @@ # coding: utf-8 -# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -18,9 +18,8 @@ # with CubicWeb. If not, see . """unit tests for module cubicweb.rset""" -from six import string_types -from six.moves import cPickle as pickle -from six.moves.urllib.parse import urlsplit +import pickle +from urllib.parse import urlsplit from rql import parse @@ -444,6 +443,78 @@ with self.assertRaises(MultipleResultsError): req.execute('Any X WHERE X is CWUser').one() + def test_first(self): + with self.admin_access.web_request() as req: + req.create_entity('CWUser', + login=u'cdevienne', + upassword=u'cdevienne', + surname=u'de Vienne', + firstname=u'Christophe') + e = req.execute('Any X WHERE X login "cdevienne"').first() + self.assertEqual(e.surname, u'de Vienne') + + e = req.execute( + 'Any X, N WHERE X login "cdevienne", X surname N').first() + self.assertEqual(e.surname, u'de Vienne') + + e = req.execute( + 'Any N, X WHERE X login "cdevienne", X surname N').first(col=1) + self.assertEqual(e.surname, u'de Vienne') + + def test_first_no_rows(self): + with self.admin_access.web_request() as req: + with self.assertRaises(NoResultError): + req.execute('Any X WHERE X login "patanok"').first() + + def test_first_multiple_rows(self): + with self.admin_access.web_request() as req: + req.create_entity( + 'CWUser', login=u'user1', upassword=u'cdevienne', + surname=u'de Vienne', firstname=u'Christophe') + req.create_entity( + 'CWUser', login=u'user2', upassword='adim', + surname=u'di mascio', firstname=u'adrien') + + e = req.execute('Any X ORDERBY X WHERE X is CWUser, ' + 'X login LIKE "user%"').first() + self.assertEqual(e.login, 'user1') + + def test_last(self): + with self.admin_access.web_request() as req: + req.create_entity('CWUser', + login=u'cdevienne', + upassword=u'cdevienne', + surname=u'de Vienne', + firstname=u'Christophe') + e = req.execute('Any X WHERE X login "cdevienne"').last() + self.assertEqual(e.surname, u'de Vienne') + + e = req.execute( + 'Any X, N WHERE X login "cdevienne", X surname N').last() + self.assertEqual(e.surname, u'de Vienne') + + e = req.execute( + 'Any N, X WHERE X login "cdevienne", X surname N').last(col=1) + self.assertEqual(e.surname, u'de Vienne') + + def test_last_no_rows(self): + with self.admin_access.web_request() as req: + with self.assertRaises(NoResultError): + req.execute('Any X WHERE X login "patanok"').last() + + def test_last_multiple_rows(self): + with self.admin_access.web_request() as req: + req.create_entity( + 'CWUser', login=u'user1', upassword=u'cdevienne', + surname=u'de Vienne', firstname=u'Christophe') + req.create_entity( + 'CWUser', login=u'user2', upassword='adim', + surname=u'di mascio', firstname=u'adrien') + + e = req.execute('Any X ORDERBY X WHERE X is CWUser, ' + 'X login LIKE "user%"').last() + self.assertEqual(e.login, 'user2') + def test_related_entity_optional(self): with self.admin_access.web_request() as req: req.create_entity('Bookmark', title=u'aaaa', path=u'path') @@ -583,17 +654,17 @@ def test_str(self): with self.admin_access.web_request() as req: rset = req.execute('(Any X,N WHERE X is CWGroup, X name N)') - self.assertIsInstance(str(rset), string_types) + self.assertIsInstance(str(rset), str) self.assertEqual(len(str(rset).splitlines()), 1) def test_repr(self): with self.admin_access.web_request() as req: rset = req.execute('(Any X,N WHERE X is CWGroup, X name N)') - self.assertIsInstance(repr(rset), string_types) + self.assertIsInstance(repr(rset), str) self.assertTrue(len(repr(rset).splitlines()) > 1) rset = req.execute('(Any X WHERE X is CWGroup, X name "managers")') - self.assertIsInstance(str(rset), string_types) + self.assertIsInstance(str(rset), str) self.assertEqual(len(str(rset).splitlines()), 1) def test_slice(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_rtags.py --- a/cubicweb/test/unittest_rtags.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_rtags.py Fri May 24 16:29:14 2019 +0200 @@ -137,18 +137,6 @@ {'key0': 'val00', 'key4': 'val4'}) -class DeprecatedInstanceWithoutModule(BaseTestCase): - - def test_deprecated_instance_without_module(self): - class SubRelationTags(RelationTags): - pass - with self.assertWarnsRegex( - DeprecationWarning, - 'instantiate SubRelationTags with __module__=__name__', - ): - SubRelationTags() - - if __name__ == '__main__': import unittest unittest.main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_schema.py --- a/cubicweb/test/unittest_schema.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_schema.py Fri May 24 16:29:14 2019 +0200 @@ -171,7 +171,7 @@ expected_entities = [ 'Ami', 'BaseTransition', 'BigInt', 'Bookmark', 'Boolean', 'Bytes', 'Card', 'Date', 'Datetime', 'Decimal', - 'CWCache', 'CWComputedRType', 'CWConstraint', + 'CWComputedRType', 'CWConstraint', 'CWConstraintType', 'CWDataImport', 'CWEType', 'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation', 'CWPermission', 'CWProperty', 'CWRType', 'CWSession', @@ -227,7 +227,7 @@ 'specializes', 'start_timestamp', 'state_of', 'status', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis', - 'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', + 'tags', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type', 'upassword', 'update_permission', 'url', 'uri', 'use_email', @@ -457,6 +457,20 @@ self.assertNotEqual(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0)) + def test_has_update_permission(self): + expr = ERQLExpression('P use_email X, U has_update_permission P') + rql, found, keyarg = expr.transform_has_permission() + self.assertEqual(rql, 'Any X,P WHERE P use_email X, X eid %(x)s') + self.assertEqual(found, [(u'update', 1)]) + self.assertEqual(keyarg, None) + + def test_expression(self): + expr = ERQLExpression('U use_email X') + rql, found, keyarg = expr.transform_has_permission() + self.assertEqual(rql, 'Any X WHERE EXISTS(U use_email X, X eid %(x)s, U eid %(u)s)') + self.assertEqual(found, None) + self.assertEqual(keyarg, None) + class GuessRrqlExprMainVarsTC(TestCase): def test_exists(self): @@ -512,7 +526,6 @@ ('cw_source', 'BaseTransition', 'CWSource', 'object'), ('cw_source', 'Bookmark', 'CWSource', 'object'), ('cw_source', 'CWAttribute', 'CWSource', 'object'), - ('cw_source', 'CWCache', 'CWSource', 'object'), ('cw_source', 'CWComputedRType', 'CWSource', 'object'), ('cw_source', 'CWConstraint', 'CWSource', 'object'), ('cw_source', 'CWConstraintType', 'CWSource', 'object'), @@ -576,32 +589,5 @@ for r, role in schema[etype].composite_rdef_roles])) -class CWShemaTC(CubicWebTC): - - def test_transform_has_permission_match(self): - with self.admin_access.repo_cnx() as cnx: - eschema = cnx.vreg.schema['EmailAddress'] - rql_exprs = eschema.get_rqlexprs('update') - self.assertEqual(len(rql_exprs), 1) - self.assertEqual(rql_exprs[0].expression, - 'P use_email X, U has_update_permission P') - rql, found, keyarg = rql_exprs[0].transform_has_permission() - self.assertEqual(rql, 'Any X,P WHERE P use_email X, X eid %(x)s') - self.assertEqual(found, [(u'update', 1)]) - self.assertEqual(keyarg, None) - - def test_transform_has_permission_no_match(self): - with self.admin_access.repo_cnx() as cnx: - eschema = cnx.vreg.schema['EmailAddress'] - rql_exprs = eschema.get_rqlexprs('read') - self.assertEqual(len(rql_exprs), 1) - self.assertEqual(rql_exprs[0].expression, - 'U use_email X') - rql, found, keyarg = rql_exprs[0].transform_has_permission() - self.assertEqual(rql, 'Any X WHERE EXISTS(U use_email X, X eid %(x)s, U eid %(u)s)') - self.assertEqual(found, None) - self.assertEqual(keyarg, None) - - if __name__ == '__main__': unittest_main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_statsd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/test/unittest_statsd.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# copyright 2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""unit tests for module cubicweb.statsd_logger""" + +import threading +import socket +import time +import re + +from unittest import TestCase +from cubicweb import statsd_logger as statsd + + +UDP_PORT = None +RUNNING = True +SOCK = socket.socket(socket.AF_INET, + socket.SOCK_DGRAM) +SOCK.settimeout(0.1) +STATSD = None +DATA = [] + + +def statsd_rcv(): + while RUNNING: + try: + data, addr = SOCK.recvfrom(1024) + if data: + rcv = [row.strip().decode() for row in data.splitlines()] + DATA.extend(rcv) + except socket.timeout: + pass + + +def setUpModule(*args): + global UDP_PORT, STATSD + SOCK.bind(('127.0.0.1', 0)) + UDP_PORT = SOCK.getsockname()[1] + STATSD = threading.Thread(target=statsd_rcv) + STATSD.start() + statsd.setup('test', ('127.0.0.1', UDP_PORT)) + + +def tearDownModule(*args): + global RUNNING + RUNNING = False + STATSD.join() + statsd.teardown() + + +class StatsdTC(TestCase): + + def setUp(self): + super(StatsdTC, self).setUp() + DATA[:] = [] + + def check_received(self, value): + for i in range(10): + if value in DATA: + break + time.sleep(0.01) + else: + self.assertIn(value, DATA) + + def check_received_ms(self, value): + value = re.compile(value.replace('?', r'\d')) + for i in range(10): + if [x for x in DATA if value.match(x)]: + break + time.sleep(0.01) + else: + self.assertTrue([x for x in DATA if value.match(x)], DATA) + + def test_statsd_c(self): + statsd.statsd_c('context') + self.check_received('test.context:1|c') + statsd.statsd_c('context', 10) + self.check_received('test.context:10|c') + + def test_statsd_g(self): + statsd.statsd_g('context', 42) + self.check_received('test.context:42|g') + statsd.statsd_g('context', 'Igorrr') + self.check_received('test.context:Igorrr|g') + + def test_statsd_t(self): + statsd.statsd_t('context', 1) + self.check_received('test.context:1.0000|ms') + statsd.statsd_t('context', 10) + self.check_received('test.context:10.0000|ms') + statsd.statsd_t('context', 0.12344) + self.check_received('test.context:0.1234|ms') + statsd.statsd_t('context', 0.12345) + self.check_received('test.context:0.1235|ms') + + def test_decorator(self): + + @statsd.statsd_timeit + def measure_me_please(): + "some nice function" + return 42 + + self.assertEqual(measure_me_please.__doc__, + "some nice function") + + measure_me_please() + self.check_received_ms('test.measure_me_please:0.0???|ms') + self.check_received('test.measure_me_please:1|c') + + def test_context_manager(self): + + with statsd.statsd_timethis('cm'): + time.sleep(0.1) + + self.check_received_ms('test.cm:100.????|ms') + self.check_received('test.cm:1|c') + + +if __name__ == '__main__': + from unittest import main + main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_uilib.py --- a/cubicweb/test/unittest_uilib.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_uilib.py Fri May 24 16:29:14 2019 +0200 @@ -23,11 +23,7 @@ import doctest import pkg_resources - -try: - from unittest import skipIf -except ImportError: - from unittest2 import skipIf +from unittest import skipIf from logilab.common.testlib import TestCase, unittest_main diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/test/unittest_utils.py --- a/cubicweb/test/unittest_utils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/test/unittest_utils.py Fri May 24 16:29:14 2019 +0200 @@ -22,13 +22,7 @@ import decimal import doctest import re -try: - from unittest2 import TestCase -except ImportError: # Python3 - from unittest import TestCase - -from six import PY2 -from six.moves import range +from unittest import TestCase from cubicweb import Binary, Unauthorized from cubicweb.devtools.testlib import CubicWebTC @@ -109,6 +103,95 @@ 'itemcount': 10, 'permanentcount': 5}) + def test_clear_on_overflow(self): + """Tests that only non-permanent items in the cache are wiped-out on ceiling overflow + """ + c = QueryCache(ceiling=10) + # set 10 values + for x in range(10): + c[x] = x + # arrange for the first 5 to be permanent + for x in range(5): + for r in range(QueryCache._maxlevel + 2): + v = c[x] + self.assertEqual(v, x) + # Add the 11-th + c[10] = 10 + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 6, + 'permanentcount': 5}) + + def test_get_with_default(self): + """ + Tests the capability of QueryCache for retrieving items with a default value + """ + c = QueryCache(ceiling=20) + # set 10 values + for x in range(10): + c[x] = x + # arrange for the first 5 to be permanent + for x in range(5): + for r in range(QueryCache._maxlevel + 2): + v = c[x] + self.assertEqual(v, x) + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 5}) + # Test defaults for existing (including in permanents) + for x in range(10): + v = c.get(x, -1) + self.assertEqual(v, x) + # Test defaults for others + for x in range(10, 15): + v = c.get(x, -1) + self.assertEqual(v, -1) + + def test_iterkeys(self): + """ + Tests the iterating on keys in the cache + """ + c = QueryCache(ceiling=20) + # set 10 values + for x in range(10): + c[x] = x + # arrange for the first 5 to be permanent + for x in range(5): + for r in range(QueryCache._maxlevel + 2): + v = c[x] + self.assertEqual(v, x) + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 5}) + keys = sorted(c) + for x in range(10): + self.assertEqual(x, keys[x]) + + def test_items(self): + """ + Tests the iterating on key-value couples in the cache + """ + c = QueryCache(ceiling=20) + # set 10 values + for x in range(10): + c[x] = x + # arrange for the first 5 to be permanent + for x in range(5): + for r in range(QueryCache._maxlevel + 2): + v = c[x] + self.assertEqual(v, x) + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 5}) + content = sorted(c.items()) + for x in range(10): + self.assertEqual(x, content[x][0]) + self.assertEqual(x, content[x][1]) + + class UStringIOTC(TestCase): def test_boolean_value(self): self.assertTrue(UStringIO()) @@ -330,9 +413,6 @@ def test_str(self): self._test(str) - if PY2: - def test_unicode(self): - self._test(unicode) def load_tests(loader, tests, ignore): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/toolsutils.py --- a/cubicweb/toolsutils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/toolsutils.py Fri May 24 16:29:14 2019 +0200 @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """some utilities for cubicweb command line tools""" -from __future__ import print_function - # XXX move most of this in logilab.common (shellutils ?) @@ -39,8 +37,6 @@ def symlink(*args): raise NotImplementedError -from six import add_metaclass - from logilab.common.clcommands import Command as BaseCommand from logilab.common.shellutils import ASK @@ -239,8 +235,7 @@ return cls -@add_metaclass(metacmdhandler) -class CommandHandler(object): +class CommandHandler(object, metaclass=metacmdhandler): """configuration specific helper for cubicweb-ctl commands""" def __init__(self, config): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/uilib.py --- a/cubicweb/uilib.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/uilib.py Fri May 24 16:29:14 2019 +0200 @@ -28,11 +28,8 @@ import re from io import StringIO -from six import PY2, PY3, text_type, binary_type, string_types, integer_types - from logilab.mtconverter import xml_escape, html_unescape from logilab.common.date import ustrftime -from logilab.common.deprecation import deprecated from cubicweb import _ from cubicweb.utils import js_dumps @@ -65,7 +62,7 @@ return value def print_int(value, req, props, displaytime=True): - return text_type(value) + return str(value) def print_date(value, req, props, displaytime=True): return ustrftime(value, req.property_value('ui.date-format')) @@ -95,7 +92,7 @@ _('%d seconds') def print_timedelta(value, req, props, displaytime=True): - if isinstance(value, integer_types): + if isinstance(value, int): # `date - date`, unlike `datetime - datetime` gives an int # (number of days), not a timedelta # XXX should rql be fixed to return Int instead of Interval in @@ -125,7 +122,7 @@ return req._('no') def print_float(value, req, props, displaytime=True): - return text_type(req.property_value('ui.float-format') % value) # XXX cast needed ? + return str(req.property_value('ui.float-format') % value) # XXX cast needed ? PRINTERS = { 'Bytes': print_bytes, @@ -143,10 +140,6 @@ 'Interval': print_timedelta, } -@deprecated('[3.14] use req.printable_value(attrtype, value, ...)') -def printable_value(req, attrtype, value, props=None, displaytime=True): - return req.printable_value(attrtype, value, props, displaytime) - def css_em_num_value(vreg, propname, default): """ we try to read an 'em' css property if we get another unit we're out of luck and resort to the given default @@ -337,11 +330,10 @@ def __init__(self, id, parent=None): self.id = id self.parent = parent - def __unicode__(self): + def __str__(self): if self.parent: return u'%s.%s' % (self.parent, self.id) - return text_type(self.id) - __str__ = __unicode__ if PY3 else lambda self: self.__unicode__().encode('utf-8') + return str(self.id) def __getattr__(self, attr): return _JSId(attr, self) def __call__(self, *args): @@ -352,14 +344,13 @@ assert isinstance(args, tuple) self.args = args self.parent = parent - def __unicode__(self): + def __str__(self): args = [] for arg in self.args: args.append(js_dumps(arg)) if self.parent: return u'%s(%s)' % (self.parent, ','.join(args)) return ','.join(args) - __str__ = __unicode__ if PY3 else lambda self: self.__unicode__().encode('utf-8') class _JS(object): def __getattr__(self, attr): @@ -392,7 +383,7 @@ 'img', 'area', 'input', 'col')) def sgml_attributes(attrs): - return u' '.join(u'%s="%s"' % (attr, xml_escape(text_type(value))) + return u' '.join(u'%s="%s"' % (attr, xml_escape(str(value))) for attr, value in sorted(attrs.items()) if value is not None) @@ -410,7 +401,7 @@ value += u' ' + sgml_attributes(attrs) if content: if escapecontent: - content = xml_escape(text_type(content)) + content = xml_escape(str(content)) value += u'>%s' % (content, tag) else: if tag in HTML4_EMPTY_TAGS: @@ -439,7 +430,7 @@ stream = StringIO() #UStringIO() don't want unicode assertion formater.format(layout, stream) res = stream.getvalue() - if isinstance(res, binary_type): + if isinstance(res, bytes): res = res.decode('UTF8') return res @@ -448,16 +439,7 @@ import traceback def exc_message(ex, encoding): - if PY3: - excmsg = str(ex) - else: - try: - excmsg = unicode(ex) - except Exception: - try: - excmsg = unicode(str(ex), encoding, 'replace') - except Exception: - excmsg = unicode(repr(ex), encoding, 'replace') + excmsg = str(ex) exctype = ex.__class__.__name__ return u'%s: %s' % (exctype, excmsg) @@ -469,8 +451,6 @@ res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3])) if stackentry[3]: data = xml_escape(stackentry[3]) - if PY2: - data = data.decode('utf-8', 'replace') res.append(u'\t %s' % data) res.append(u'\n') try: @@ -506,8 +486,6 @@ xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2]))) if stackentry[3]: string = xml_escape(stackentry[3]) - if PY2: - string = string.decode('utf-8', 'replace') strings.append(u'  %s
\n' % (string)) # add locals info for each entry try: @@ -550,16 +528,8 @@ self.wfunc(data) def writerow(self, row): - if PY3: - self.writer.writerow(row) - return - csvrow = [] - for elt in row: - if isinstance(elt, text_type): - csvrow.append(elt.encode(self.encoding)) - else: - csvrow.append(str(elt)) - self.writer.writerow(csvrow) + self.writer.writerow(row) + return def writerows(self, rows): for row in rows: @@ -575,7 +545,7 @@ def __call__(self, function): def newfunc(*args, **kwargs): ret = function(*args, **kwargs) - if isinstance(ret, string_types): + if isinstance(ret, str): return ret[:self.maxsize] return ret return newfunc @@ -584,6 +554,6 @@ def htmlescape(function): def newfunc(*args, **kwargs): ret = function(*args, **kwargs) - assert isinstance(ret, string_types) + assert isinstance(ret, str) return xml_escape(ret) return newfunc diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/utils.py --- a/cubicweb/utils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/utils.py Fri May 24 16:29:14 2019 +0200 @@ -25,24 +25,14 @@ import random import re import json - -from six import PY3 - from operator import itemgetter -if PY3: - from inspect import getfullargspec as getargspec -else: - from inspect import getargspec +from inspect import getfullargspec as getargspec from itertools import repeat from uuid import uuid4 -from warnings import warn from threading import Lock from logging import getLogger -from six import text_type - from logilab.mtconverter import xml_escape -from logilab.common.deprecation import deprecated from logilab.common.date import ustrftime from cubicweb import Binary @@ -108,7 +98,7 @@ """ def __init__(self, w, tag, closetag=None): self.written = False - self.tag = text_type(tag) + self.tag = tag self.closetag = closetag self.w = w @@ -124,7 +114,7 @@ def __exit__(self, exctype, value, traceback): if self.written is True: if self.closetag: - self.w(text_type(self.closetag)) + self.w(self.closetag) else: self.w(self.tag.replace('<', '= self._max: - logger.warning('Cache %s is full.' % id(self)) - self._clear() + if len(self._permanent) >= self._max: + # we really are full with permanents => clear + logger.warning('Cache %s is full.' % id(self)) + self._clear() + else: + # pathological case where _transient was probably empty ... + # drop all non-permanents + to_drop = set(self._data.keys()).difference(self._permanent) + for k in to_drop: + # should not be in _transient + assert k not in self._transient + self._data.pop(k, None) def _usage_report(self): with self._lock: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/view.py --- a/cubicweb/view.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/view.py Fri May 24 16:29:14 2019 +0200 @@ -24,9 +24,6 @@ from warnings import warn from functools import partial -from six.moves import range - -from logilab.common.deprecation import deprecated from logilab.common.registry import yes from logilab.mtconverter import xml_escape diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/vregistry.py --- a/cubicweb/vregistry.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -from warnings import warn -from logilab.common.deprecation import class_moved -warn('[3.15] moved to logilab.common.registry', DeprecationWarning, stacklevel=2) -from logilab.common.registry import * - -VRegistry = class_moved(RegistryStore, old_name='VRegistry', message='[3.15] VRegistry moved to logilab.common.registry as RegistryStore') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/__init__.py --- a/cubicweb/web/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -19,12 +19,9 @@ publisher to get a full CubicWeb web application """ +from urllib.parse import quote as urlquote from cubicweb import _ - -from six.moves.urllib.parse import quote as urlquote -from logilab.common.deprecation import deprecated - from cubicweb.web._exceptions import * from cubicweb.utils import json_dumps from cubicweb.uilib import eid_param diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/_exceptions.py --- a/cubicweb/web/_exceptions.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/_exceptions.py Fri May 24 16:29:14 2019 +0200 @@ -18,9 +18,7 @@ # with CubicWeb. If not, see . """exceptions used in the core of the CubicWeb web application""" - - -from six.moves import http_client +import http.client as http_client from cubicweb._exceptions import * from cubicweb.utils import json_dumps @@ -28,15 +26,18 @@ class DirectResponse(Exception): """Used to supply a twitted HTTP Response directly""" + def __init__(self, response): self.response = response + class InvalidSession(CubicWebException): """raised when a session id is found but associated session is not found or invalid""" # Publish related exception + class PublishException(CubicWebException): """base class for publishing related exception""" @@ -44,18 +45,23 @@ self.status = kwargs.pop('status', http_client.OK) super(PublishException, self).__init__(*args, **kwargs) + class LogOut(PublishException): """raised to ask for deauthentication of a logged in user""" + def __init__(self, url=None): super(LogOut, self).__init__() self.url = url + class Redirect(PublishException): """raised to redirect the http request""" + def __init__(self, location, status=http_client.SEE_OTHER): super(Redirect, self).__init__(status=status) self.location = location + class StatusResponse(PublishException): def __init__(self, status, content=''): @@ -67,6 +73,7 @@ # Publish related error + class RequestError(PublishException): """raised when a request can't be served because of a bad input""" @@ -82,13 +89,16 @@ kwargs.setdefault('status', http_client.BAD_REQUEST) super(NothingToEdit, self).__init__(*args, **kwargs) + class ProcessFormError(RequestError): """raised when posted data can't be processed by the corresponding field """ + def __init__(self, *args, **kwargs): kwargs.setdefault('status', http_client.BAD_REQUEST) super(ProcessFormError, self).__init__(*args, **kwargs) + class NotFound(RequestError): """raised when something was not found. In most case, a 404 error should be returned""" @@ -97,9 +107,11 @@ kwargs.setdefault('status', http_client.NOT_FOUND) super(NotFound, self).__init__(*args, **kwargs) + class RemoteCallFailed(RequestError): """raised when a json remote call fails """ + def __init__(self, reason='', status=http_client.INTERNAL_SERVER_ERROR): super(RemoteCallFailed, self).__init__(reason, status=status) self.reason = reason diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/application.py --- a/cubicweb/web/application.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/application.py Fri May 24 16:29:14 2019 +0200 @@ -19,15 +19,11 @@ import contextlib -from functools import wraps +import http.client as http_client import json import sys from time import clock, time from contextlib import contextmanager -from warnings import warn - -from six import PY2, text_type, binary_type -from six.moves import http_client from rql import BadRQLQuery @@ -38,42 +34,14 @@ from cubicweb.repoapi import anonymous_cnx from cubicweb.web import cors from cubicweb.web import ( - LOGGER, StatusResponse, DirectResponse, Redirect, NotFound, LogOut, + LOGGER, DirectResponse, Redirect, NotFound, LogOut, RemoteCallFailed, InvalidSession, RequestError, PublishException) -from cubicweb.web.request import CubicWebRequestBase # make session manager available through a global variable so the debug view can # print information about web session SESSION_MANAGER = None -def _deprecated_path_arg(func): - @wraps(func) - def wrapper(self, req, *args, **kwargs): - if args or 'path' in kwargs: - func_name = func.func_name if PY2 else func.__name__ - warn('[3.24] path argument got removed from "%s" parameters' % func_name, - DeprecationWarning) - path = args[0] if args else kwargs['path'] - assert path == req.relative_path(False), \ - 'mismatching path, {0} vs {1}'.format(path, req.relative_path(False)) - return func(self, req) - return wrapper - - -def _deprecated_req_path_swapped(func): - @wraps(func) - def wrapper(self, req, *args, **kwargs): - if not isinstance(req, CubicWebRequestBase): - warn('[3.15] Application entry point arguments are now (req, path) ' - 'not (path, req)', DeprecationWarning, 2) - path = req - req = args[0] if args else kwargs.pop('req') - args = (path, ) + args[1:] - return func(self, req, *args, **kwargs) - return wrapper - - @contextmanager def anonymized_request(req): from cubicweb.web.views.authentication import Session @@ -227,7 +195,6 @@ # publish methods ######################################################### - @_deprecated_path_arg def log_handle_request(self, req): """wrapper around _publish to log all queries executed for a given accessed path @@ -273,8 +240,6 @@ except Exception: self.exception('error while logging queries') - @_deprecated_req_path_swapped - @_deprecated_path_arg def main_handle_request(self, req): """Process an HTTP request `req` @@ -349,10 +314,9 @@ # XXX ensure we don't actually serve content if not content: content = self.need_login_content(req) - assert isinstance(content, binary_type) + assert isinstance(content, bytes) return content - @_deprecated_path_arg def core_handle(self, req): """method called by the main publisher to process relative path @@ -390,11 +354,6 @@ # Return directly an empty 200 req.status_out = 200 result = b'' - except StatusResponse as ex: - warn('[3.16] StatusResponse is deprecated use req.status_out', - DeprecationWarning, stacklevel=2) - result = ex.content - req.status_out = ex.status except Redirect as ex: # Redirect may be raised by edit controller when everything went # fine, so attempt to commit @@ -520,7 +479,7 @@ if req.status_out < 400: # don't overwrite it if it's already set req.status_out = status - json_dumper = getattr(ex, 'dumps', lambda: json.dumps({'reason': text_type(ex)})) + json_dumper = getattr(ex, 'dumps', lambda: json.dumps({'reason': str(ex)})) return json_dumper().encode('utf-8') # special case handling diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/box.py --- a/cubicweb/web/box.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/box.py Fri May 24 16:29:14 2019 +0200 @@ -20,19 +20,20 @@ from cubicweb import _ -from six import add_metaclass - from logilab.mtconverter import xml_escape -from logilab.common.deprecation import class_deprecated, class_renamed +from logilab.common.deprecation import class_deprecated from cubicweb import Unauthorized, role as get_role from cubicweb.schema import display_name from cubicweb.predicates import no_cnx, one_line_rset from cubicweb.view import View -from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget, - RawBoxItem, BoxSeparator) + RawBoxItem) from cubicweb.web.action import UnregisteredAction +from cubicweb.web.component import ( + EditRelationMixIn, + Separator, +) def sort_by_category(actions, categories_in_order=None): @@ -41,13 +42,13 @@ actions_by_cat = {} for action in actions: actions_by_cat.setdefault(action.category, []).append( - (action.title, action) ) + (action.title, action)) for key, values in actions_by_cat.items(): actions_by_cat[key] = [act for title, act in sorted(values, key=lambda x: x[0])] if categories_in_order: for cat in categories_in_order: if cat in actions_by_cat: - result.append( (cat, actions_by_cat[cat]) ) + result.append((cat, actions_by_cat[cat])) for item in sorted(actions_by_cat.items()): result.append(item) return result @@ -55,8 +56,7 @@ # old box system, deprecated ################################################### -@add_metaclass(class_deprecated) -class BoxTemplate(View): +class BoxTemplate(View, metaclass=class_deprecated): """base template for boxes, usually a (contextual) list of possible actions. Various classes attributes may be used to control the box rendering. @@ -69,7 +69,9 @@ box.render(self.w) """ - __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)' + __deprecation_warning__ = ( + '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)' + ) __registry__ = 'ctxcomponents' __select__ = ~no_cnx() @@ -78,13 +80,13 @@ cw_property_defs = { _('visible'): dict(type='Boolean', default=True, help=_('display the box or not')), - _('order'): dict(type='Int', default=99, - help=_('display order of the box')), + _('order'): dict(type='Int', default=99, + help=_('display order of the box')), # XXX 'incontext' boxes are handled by the default primary view _('context'): dict(type='String', default='left', vocabulary=(_('left'), _('incontext'), _('right')), help=_('context where this box should be displayed')), - } + } context = 'left' def sort_actions(self, actions): @@ -161,8 +163,6 @@ """classes inheriting from EntityBoxTemplate should define cell_call""" self.cell_call(row, col, **kwargs) -from cubicweb.web.component import AjaxEditRelationCtxComponent, EditRelationMixIn - class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate): """base class for boxes which let add or remove entities linked @@ -172,6 +172,7 @@ class attributes. """ rtype = None + def cell_call(self, row, col, view=None, **kwargs): self._cw.add_js('cubicweb.ajax.js') entity = self.cw_rset.get_entity(row, col) @@ -182,7 +183,7 @@ unrelated = self.unrelated_boxitems(entity) box.extend(related) if related and unrelated: - box.append(BoxSeparator()) + box.append(Separator()) box.extend(unrelated) box.render(self.w) @@ -190,8 +191,3 @@ label = super(EditRelationBoxTemplate, self).box_item( entity, etarget, rql, label) return RawBoxItem(label, liclass=u'invisible') - - -AjaxEditRelationBoxTemplate = class_renamed( - 'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent, - '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent (%(cls)s)') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/captcha.py --- a/cubicweb/web/captcha.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/captcha.py Fri May 24 16:29:14 2019 +0200 @@ -24,8 +24,6 @@ from random import randint, choice from io import BytesIO -from six.moves import range - from PIL import Image, ImageFont, ImageDraw, ImageFilter diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/component.py --- a/cubicweb/web/component.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/component.py Fri May 24 16:29:14 2019 +0200 @@ -24,8 +24,6 @@ from warnings import warn -from six import PY3, add_metaclass, text_type - from logilab.common.deprecation import class_deprecated, class_renamed, deprecated from logilab.mtconverter import xml_escape @@ -239,12 +237,9 @@ self.label = label self.attrs = attrs - def __unicode__(self): + def __str__(self): return tags.a(self.label, href=self.href, **self.attrs) - if PY3: - __str__ = __unicode__ - def render(self, w): w(tags.a(self.label, href=self.href, **self.attrs)) @@ -455,7 +450,7 @@ @property def domid(self): - return domid(self.__regid__) + text_type(self.entity.eid) + return domid(self.__regid__) + str(self.entity.eid) def lazy_view_holder(self, w, entity, oid, registry='views'): """add a holder and return a URL that may be used to replace this @@ -528,7 +523,7 @@ args['subject'], args['object']) return u'[%s] %s' % ( - xml_escape(text_type(jscall)), label, etarget.view('incontext')) + xml_escape(str(jscall)), label, etarget.view('incontext')) def related_boxitems(self, entity): return [self.box_item(entity, etarget, 'delete_relation', u'-') @@ -545,7 +540,7 @@ """returns the list of unrelated entities, using the entity's appropriate vocabulary function """ - skip = set(text_type(e.eid) for e in entity.related(self.rtype, role(self), + skip = set(str(e.eid) for e in entity.related(self.rtype, role(self), entities=True)) skip.add(None) skip.add(INTERNAL_FIELD_VALUE) @@ -663,7 +658,7 @@ if maydel: if not js_css_added: js_css_added = self.add_js_css() - jscall = text_type(js.ajaxBoxRemoveLinkedEntity( + jscall = str(js.ajaxBoxRemoveLinkedEntity( self.__regid__, entity.eid, rentity.eid, self.fname_remove, self.removed_msg and _(self.removed_msg))) @@ -678,7 +673,7 @@ if mayadd: multiple = self.rdef.role_cardinality(self.role) in '*+' w(u'""") - line = u''.join(u'' % col for col in self.columns) - self.w(line % infos) - self.w(u'\n') - - def table_header(self, sample): - """builds the table's header""" - self.w(u'') - for column in self.columns: - meth = getattr(self, 'header_for_%s' % column, None) - if meth: - colname = meth(sample) - else: - colname = self._cw._(column) - self.w(u'' % xml_escape(colname)) - self.w(u'\n') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/tabs.py --- a/cubicweb/web/views/tabs.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/tabs.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six import string_types - from logilab.common.deprecation import class_renamed from logilab.mtconverter import xml_escape @@ -116,7 +114,7 @@ active_tab = uilib.domid(default_tab) viewsvreg = self._cw.vreg['views'] for tab in tabs: - if isinstance(tab, string_types): + if isinstance(tab, str): tabid, tabkwargs = tab, {} else: tabid, tabkwargs = tab diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/timeline.py --- a/cubicweb/web/views/timeline.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . - -try: - from cubes.timeline.views import ( - TimelineJsonView, - TimelineViewMixIn, - TimelineView, - StaticTimelineView) - -except ImportError: - pass -else: - from logilab.common.deprecation import class_moved - - TimelineJsonView = class_moved(TimelineJsonView, 'TimelineJsonView') - TimelineViewMixIn = class_moved(TimelineViewMixIn, 'TimelineViewMixIn') - TimelineView = class_moved(TimelineView, 'TimelineView') - StaticTimelineView = class_moved(StaticTimelineView, 'StaticTimelineView') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/timetable.py --- a/cubicweb/web/views/timetable.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/timetable.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six.moves import range - from logilab.mtconverter import xml_escape from logilab.common.date import ONEDAY, date_range, todatetime diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/treeview.py --- a/cubicweb/web/views/treeview.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/treeview.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ from cubicweb import _ -from warnings import warn - from logilab.mtconverter import xml_escape from cubicweb.utils import make_uid, json @@ -150,11 +148,7 @@ jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid) def call(self, subvid=None, treeid=None, - initial_load=True, initial_thru_ajax=None, **morekwargs): - if initial_thru_ajax is not None: - msg = '[3.24] initial_thru_ajax argument is deprecated' - warn(msg, DeprecationWarning, stacklevel=2) - + initial_load=True, **morekwargs): subvid, treeid = self._init_params(subvid, treeid, initial_load, morekwargs) ulid = ' ' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/uicfg.py --- a/cubicweb/web/views/uicfg.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/uicfg.py Fri May 24 16:29:14 2019 +0200 @@ -56,8 +56,6 @@ from itertools import repeat -from six import string_types - from cubicweb import neg_role from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet, RelationTagsDict, NoTargetRelationTagsDict, @@ -692,7 +690,7 @@ self.tag_relation((sschema, rschema, oschema, role), True) def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs): - if isinstance(attr, string_types): + if isinstance(attr, str): attr, role = attr, 'subject' else: attr, role = attr diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/urlrewrite.py --- a/cubicweb/web/views/urlrewrite.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/urlrewrite.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ import re -from six import string_types, add_metaclass - from cubicweb.uilib import domid from cubicweb.appobject import AppObject @@ -53,8 +51,7 @@ return super(metarewriter, mcs).__new__(mcs, name, bases, classdict) -@add_metaclass(metarewriter) -class URLRewriter(AppObject): +class URLRewriter(AppObject, metaclass=metarewriter): """Base class for URL rewriters. Url rewriters should have a `rules` dict that maps an input URI @@ -124,14 +121,14 @@ required_groups = None if required_groups and not req.user.matching_groups(required_groups): continue - if isinstance(inputurl, string_types): + if isinstance(inputurl, str): if inputurl == uri: req.form.update(infos) break elif inputurl.match(uri): # it's a regexp # XXX what about i18n? (vtitle for instance) for param, value in infos.items(): - if isinstance(value, string_types): + if isinstance(value, str): req.form[param] = inputurl.sub(value, uri) else: req.form[param] = value @@ -224,7 +221,7 @@ required_groups = None if required_groups and not req.user.matching_groups(required_groups): continue - if isinstance(inputurl, string_types): + if isinstance(inputurl, str): if inputurl == uri: return callback(inputurl, uri, req, self._cw.vreg.schema) elif inputurl.match(uri): # it's a regexp diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/wdoc.py --- a/cubicweb/web/views/wdoc.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/wdoc.py Fri May 24 16:29:14 2019 +0200 @@ -26,8 +26,6 @@ from os.path import join from xml.etree.ElementTree import parse -from six import text_type - from logilab.common.registry import yes from cubicweb.predicates import match_form_params @@ -91,9 +89,9 @@ for title in node.findall('title'): title_lang = title.attrib['{http://www.w3.org/XML/1998/namespace}lang'] if title_lang == lang: - return text_type(title.text) + return title.text if title_lang == 'en': - fallback_title = text_type(title.text) + fallback_title = title.text return fallback_title diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/workflow.py --- a/cubicweb/web/views/workflow.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/workflow.py Fri May 24 16:29:14 2019 +0200 @@ -24,12 +24,7 @@ from cubicweb import _ -import os - -from six import add_metaclass, text_type - from logilab.mtconverter import xml_escape -from logilab.common.deprecation import class_deprecated from cubicweb import Unauthorized from cubicweb.predicates import (one_line_rset, @@ -38,7 +33,6 @@ from cubicweb.view import EntityView from cubicweb.web import stdmsgs, action, component, form from cubicweb.web import formwidgets as fwdgs -from cubicweb.web.views import TmpFileViewMixin from cubicweb.web.views import uicfg, forms, ibreadcrumbs from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler @@ -313,7 +307,7 @@ wf = req.entity_from_eid(wfeid) rschema = req.vreg.schema[field.name] param = 'toeid' if field.role == 'subject' else 'fromeid' - return sorted((e.view('combobox'), text_type(e.eid)) + return sorted((e.view('combobox'), str(e.eid)) for e in getattr(wf, 'reverse_%s' % wfrelation) if rschema.has_perm(req, 'add', **{param: e.eid})) @@ -444,24 +438,3 @@ def build_dotpropshandler(self): return WorkflowDotPropsHandler(self._cw) - - -@add_metaclass(class_deprecated) -class TmpPngView(TmpFileViewMixin, EntityView): - __deprecation_warning__ = '[3.18] %(cls)s is deprecated' - __regid__ = 'tmppng' - __select__ = match_form_params('tmpfile') - content_type = 'image/png' - binary = True - - def cell_call(self, row=0, col=0): - key = self._cw.form['tmpfile'] - if key not in self._cw.session.data: - # the temp file is gone and there's nothing - # we can do about it - # we should probably write it to some well - # behaved place and serve it - return - tmpfile = self._cw.session.data.pop(key) - self.w(open(tmpfile, 'rb').read()) - os.unlink(tmpfile) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/xbel.py --- a/cubicweb/web/views/xbel.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/xbel.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six.moves import range - from logilab.mtconverter import xml_escape from cubicweb.predicates import is_instance diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/xmlrss.py --- a/cubicweb/web/views/xmlrss.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/xmlrss.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ from base64 import b64encode from time import timezone -from six.moves import range - from logilab.mtconverter import xml_escape from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset, diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/webconfig.py --- a/cubicweb/web/webconfig.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/webconfig.py Fri May 24 16:29:14 2019 +0200 @@ -24,37 +24,32 @@ import hmac from uuid import uuid4 from os.path import dirname, join, exists, split, isdir -from warnings import warn - -from six import text_type from logilab.common.decorators import cached, cachedproperty -from logilab.common.deprecation import deprecated -from logilab.common.configuration import merge_options +from logilab.common.configuration import Method, merge_options from cubicweb import ConfigurationError -from cubicweb.toolsutils import read_config from cubicweb.cwconfig import CubicWebConfiguration, register_persistent_options _DATA_DIR = join(dirname(__file__), 'data') -register_persistent_options( ( +register_persistent_options(( # site-wide only web ui configuration ('site-title', - {'type' : 'string', 'default': 'unset title', + {'type': 'string', 'default': 'unset title', 'help': _('site title'), 'sitewide': True, 'group': 'ui', }), ('main-template', - {'type' : 'string', 'default': 'main-template', + {'type': 'string', 'default': 'main-template', 'help': _('id of main template used to render pages'), 'sitewide': True, 'group': 'ui', }), # user web ui configuration ('fckeditor', - {'type' : 'yn', 'default': False, + {'type': 'yn', 'default': False, 'help': _('should html fields being edited using fckeditor (a HTML ' 'WYSIWYG editor). You should also select text/html as default ' 'text format to actually get fckeditor.'), @@ -62,23 +57,23 @@ }), # navigation configuration ('page-size', - {'type' : 'int', 'default': 40, + {'type': 'int', 'default': 40, 'help': _('maximum number of objects displayed by page of results'), 'group': 'navigation', }), ('related-limit', - {'type' : 'int', 'default': 8, + {'type': 'int', 'default': 8, 'help': _('maximum number of related entities to display in the primary ' 'view'), 'group': 'navigation', }), ('combobox-limit', - {'type' : 'int', 'default': 20, + {'type': 'int', 'default': 20, 'help': _('maximum number of entities to display in related combo box'), 'group': 'navigation', }), - )) +)) class BaseWebConfiguration(CubicWebConfiguration): @@ -88,7 +83,7 @@ options = merge_options(CubicWebConfiguration.options + ( ('repository-uri', - {'type' : 'string', + {'type': 'string', 'default': 'inmemory://', 'help': 'see `cubicweb.dbapi.connect` documentation for possible value', 'group': 'web', 'level': 2, @@ -99,26 +94,27 @@ 'group': 'ui', 'level': 2, }), ('anonymous-user', - {'type' : 'string', + {'type': 'string', 'default': None, - 'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)', + 'help': ('login of the CubicWeb user account to use for anonymous ' + 'user (if you want to allow anonymous)'), 'group': 'web', 'level': 1, }), ('anonymous-password', - {'type' : 'string', + {'type': 'string', 'default': None, 'help': 'password of the CubicWeb user account to use for anonymous user, ' 'if anonymous-user is set', 'group': 'web', 'level': 1, }), ('query-log-file', - {'type' : 'string', + {'type': 'string', 'default': None, 'help': 'web instance query log file', 'group': 'web', 'level': 3, }), ('cleanup-anonymous-session-time', - {'type' : 'time', + {'type': 'time', 'default': '5min', 'help': 'Same as cleanup-session-time but specific to anonymous ' 'sessions. You can have a much smaller timeout here since it will be ' @@ -134,10 +130,8 @@ allowed or if an empty login is used in configuration """ try: - user = self['anonymous-user'] or None + user = self['anonymous-user'] or None passwd = self['anonymous-password'] - if user: - user = text_type(user) except KeyError: user, passwd = None, None except UnicodeDecodeError: @@ -145,7 +139,6 @@ return user, passwd - class WebConfiguration(BaseWebConfiguration): """the WebConfiguration is a singleton object handling instance's configuration and preferences @@ -160,20 +153,20 @@ 'group': 'web', }), ('auth-mode', - {'type' : 'choice', - 'choices' : ('cookie', 'http'), + {'type': 'choice', + 'choices': ('cookie', 'http'), 'default': 'cookie', 'help': 'authentication mode (cookie / http)', 'group': 'web', 'level': 3, }), ('realm', - {'type' : 'string', + {'type': 'string', 'default': 'cubicweb', 'help': 'realm to use on HTTP authentication mode', 'group': 'web', 'level': 3, }), ('http-session-time', - {'type' : 'time', + {'type': 'time', 'default': 0, 'help': "duration of the cookie used to store session identifier. " "If 0, the cookie will expire when the user exist its browser. " @@ -181,7 +174,7 @@ 'group': 'web', 'level': 2, }), ('submit-mail', - {'type' : 'string', + {'type': 'string', 'default': None, 'help': ('Mail used as recipient to report bug in this instance, ' 'if you want this feature on'), @@ -189,31 +182,32 @@ }), ('language-mode', - {'type' : 'choice', + {'type': 'choice', 'choices': ('http-negotiation', 'url-prefix', ''), 'default': 'http-negotiation', 'help': ('source for interface\'s language detection. ' 'If set to "http-negotiation" the Accept-Language HTTP header will be used,' - ' if set to "url-prefix", the URL will be inspected for a short language prefix.'), + ' if set to "url-prefix", the URL will be inspected for a' + ' short language prefix.'), 'group': 'web', 'level': 2, }), ('print-traceback', - {'type' : 'yn', + {'type': 'yn', 'default': CubicWebConfiguration.mode != 'system', 'help': 'print the traceback on the error page when an error occurred', 'group': 'web', 'level': 2, }), ('captcha-font-file', - {'type' : 'string', + {'type': 'string', 'default': join(_DATA_DIR, 'porkys.ttf'), 'help': 'True type font to use for captcha image generation (you \ must have the python imaging library installed to use captcha)', 'group': 'web', 'level': 3, }), ('captcha-font-size', - {'type' : 'int', + {'type': 'int', 'default': 25, 'help': 'Font size to use for captcha image generation (you must \ have the python imaging library installed to use captcha)', @@ -221,7 +215,7 @@ }), ('concat-resources', - {'type' : 'yn', + {'type': 'yn', 'default': False, 'help': 'use modconcat-like URLS to concat and serve JS / CSS files', 'group': 'web', 'level': 2, @@ -245,36 +239,37 @@ 'group': 'web', 'level': 2, }), ('access-control-allow-origin', - {'type' : 'csv', + {'type': 'csv', 'default': (), - 'help':('comma-separated list of allowed origin domains or "*" for any domain'), + 'help': ('comma-separated list of allowed origin domains or "*" for any domain'), 'group': 'web', 'level': 2, }), ('access-control-allow-methods', - {'type' : 'csv', + {'type': 'csv', 'default': (), 'help': ('comma-separated list of allowed HTTP methods'), 'group': 'web', 'level': 2, }), ('access-control-max-age', - {'type' : 'int', + {'type': 'int', 'default': None, 'help': ('maximum age of cross-origin resource sharing (in seconds)'), 'group': 'web', 'level': 2, }), ('access-control-expose-headers', - {'type' : 'csv', + {'type': 'csv', 'default': (), - 'help':('comma-separated list of HTTP headers the application declare in response to a preflight request'), + 'help': ('comma-separated list of HTTP headers the application ' + 'declare in response to a preflight request'), 'group': 'web', 'level': 2, }), ('access-control-allow-headers', - {'type' : 'csv', + {'type': 'csv', 'default': (), - 'help':('comma-separated list of HTTP headers the application may set in the response'), + 'help': ('comma-separated list of HTTP headers the application may set in the response'), 'group': 'web', 'level': 2, }), - )) + )) def __init__(self, *args, **kwargs): super(WebConfiguration, self).__init__(*args, **kwargs) @@ -292,10 +287,6 @@ continue yield key, pdef - @deprecated('[3.22] call req.cnx.repo.get_versions() directly') - def vc_config(self): - return self.repository().get_versions() - @cachedproperty def _instance_salt(self): """This random key/salt is used to sign content to be sent back by @@ -306,12 +297,13 @@ def sign_text(self, text): """sign some text for later checking""" # hmac.new expect bytes - if isinstance(text, text_type): + if isinstance(text, str): text = text.encode('utf-8') # replace \r\n so we do not depend on whether a browser "reencode" # original message using \r\n or not return hmac.new(self._instance_salt, - text.strip().replace(b'\r\n', b'\n')).hexdigest() + text.strip().replace(b'\r\n', b'\n'), + digestmod="sha3_512").hexdigest() def check_text_sign(self, text, signature): """check the text signature is equal to the given signature""" @@ -343,14 +335,9 @@ if directory is None: return None, None if self['use-uicache'] and rdirectory == 'data' and rid.endswith('.css'): - if rid == 'cubicweb.old.css': - # @import('cubicweb.css') in css - warn('[3.20] cubicweb.old.css has been renamed back to cubicweb.css', - DeprecationWarning) - rid = 'cubicweb.css' return self.ensure_uid_directory( - self.uiprops.process_resource( - join(directory, rdirectory), rid)), rid + self.uiprops.process_resource( + join(directory, rdirectory), rid)), rid return join(directory, rdirectory), rid def locate_all_files(self, rid, rdirectory='wdoc'): @@ -408,14 +395,6 @@ self._load_ui_properties_file(uiprops, path) self._load_ui_properties_file(uiprops, self.apphome) datadir_url = uiprops.context['datadir_url'] - if (datadir_url+'/cubicweb.old.css') in uiprops['STYLESHEETS']: - warn('[3.20] cubicweb.old.css has been renamed back to cubicweb.css', - DeprecationWarning) - idx = uiprops['STYLESHEETS'].index(datadir_url+'/cubicweb.old.css') - uiprops['STYLESHEETS'][idx] = datadir_url+'/cubicweb.css' - if datadir_url+'/cubicweb.reset.css' in uiprops['STYLESHEETS']: - warn('[3.20] cubicweb.reset.css is obsolete', DeprecationWarning) - uiprops['STYLESHEETS'].remove(datadir_url+'/cubicweb.reset.css') cubicweb_js_url = datadir_url + '/cubicweb.js' if cubicweb_js_url not in uiprops['JAVASCRIPTS']: uiprops['JAVASCRIPTS'].insert(0, cubicweb_js_url) @@ -453,3 +432,39 @@ def static_file_del(self, rpath): if self.static_file_exists(rpath): os.remove(join(self.static_directory, rpath)) + + +class WebConfigurationBase(WebConfiguration): + """web instance (in a web server) client of a RQL server""" + + options = merge_options(( + # ctl configuration + ('port', + {'type': 'int', + 'default': None, + 'help': 'http server port number (default to 8080)', + 'group': 'web', 'level': 0, + }), + ('interface', + {'type': 'string', + 'default': '0.0.0.0', + 'help': 'http server address on which to listen (default to everywhere)', + 'group': 'web', 'level': 1, + }), + ('max-post-length', # XXX specific to "wsgi" server + {'type': 'bytes', + 'default': '100MB', + 'help': 'maximum length of HTTP request. Default to 100 MB.', + 'group': 'web', 'level': 1, + }), + ('pid-file', + {'type': 'string', + 'default': Method('default_pid_file'), + 'help': 'repository\'s pid file', + 'group': 'main', 'level': 2, + }), + ) + WebConfiguration.options) + + def default_base_url(self): + from socket import getfqdn + return 'http://%s:%s/' % (self['host'] or getfqdn().lower(), self['port'] or 8080) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/webctl.py --- a/cubicweb/web/webctl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/webctl.py Fri May 24 16:29:14 2019 +0200 @@ -18,7 +18,6 @@ """cubicweb-ctl commands and command handlers common to twisted/modpython web configuration """ -from __future__ import print_function import os import os.path as osp diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/wfutils.py --- a/cubicweb/wfutils.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/wfutils.py Fri May 24 16:29:14 2019 +0200 @@ -45,8 +45,6 @@ import collections -from six import text_type - from cubicweb import NoResultError @@ -91,7 +89,6 @@ by calling :func:`cleanupworkflow`. :return: The created/updated workflow entity """ - name = text_type(name) try: wf = cnx.find('Workflow', name=name).one() except NoResultError: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/wsgi/__init__.py --- a/cubicweb/wsgi/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/wsgi/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -27,9 +27,9 @@ from email import message, message_from_string +from http.cookies import SimpleCookie from pprint import pformat as _pformat -from six.moves.http_cookies import SimpleCookie def pformat(obj): """pretty prints `obj` if possible""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/wsgi/handler.py --- a/cubicweb/wsgi/handler.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/wsgi/handler.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ from itertools import chain, repeat -from six.moves import zip - from cubicweb import AuthenticationError from cubicweb.web import DirectResponse from cubicweb.web.application import CubicWebPublisher diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/wsgi/request.py --- a/cubicweb/wsgi/request.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/wsgi/request.py Fri May 24 16:29:14 2019 +0200 @@ -28,8 +28,7 @@ import tempfile from io import BytesIO - -from six.moves.urllib.parse import parse_qs +from urllib.parse import parse_qs from cubicweb.multipart import ( copy_file, parse_form_data, parse_options_header) @@ -88,7 +87,7 @@ # robust against potentially malformed input. form = pformat(self.form) meta = pformat(self.environ) - return '' % \ + return '' % \ (form, meta) ## cubicweb request interface ################################################ diff -r ba5231e1aa45 -r 32ee89340e59 debian/control --- a/debian/control Tue May 21 10:50:08 2019 +0200 +++ b/debian/control Fri May 24 16:29:14 2019 +0200 @@ -14,7 +14,6 @@ python-docutils, python-sphinx, python-logilab-common (>= 1.4.0), - python-unittest2 (>= 0.7.0), python-logilab-mtconverter, python-markdown, python-tz, @@ -28,6 +27,7 @@ python-passlib, python-repoze.lru, python-wsgicors, + python-filelock, sphinx-common, Standards-Version: 3.9.6 Homepage: https://www.cubicweb.org @@ -45,12 +45,12 @@ python-logilab-database (>= 1.15.0), python-yams (>= 0.45.0), python-rql (>= 0.34.0), - python-unittest2 (>= 0.7.0), python-lxml, python-markdown, python-passlib, python-tz, graphviz, + python-filelock, gettext, Recommends: cubicweb-ctl (= ${source:Version}), @@ -59,7 +59,7 @@ python-cubicweb-pyramid (= ${source:Version}), # common recommends python-simpletal (>= 4.0), - python-crypto, + python-pycryptodome, # web recommends (mostly) python-docutils (>= 0.6), python-vobject, @@ -73,21 +73,18 @@ Suggests: python-zmq, python-cwclientlib (>= 0.4.0), - python-cubicweb-twisted (= ${source:Version}), python-cubicweb-documentation (= ${source:Version}), w3c-dtd-xhtml, xvfb, Replaces: cubicweb (<< 3.24.0-1~), cubicweb-server (<< 3.24.0-1~), - cubicweb-twisted (<< 3.24.0-1~), cubicweb-web (<< 3.24.0-1~), cubicweb-core, cubicweb-common (<< 3.24.0-1~), Breaks: cubicweb (<< 3.24.0-1~), cubicweb-server (<< 3.24.0-1~), - cubicweb-twisted (<< 3.24.0-1~), cubicweb-inlinedit (<< 1.1.1), cubicweb-bootstrap (<< 0.6.6), cubicweb-folder (<< 1.10.0), @@ -138,18 +135,6 @@ cubicweb repository. -Package: python-cubicweb-twisted -Architecture: all -Depends: - python-cubicweb (= ${source:Version}), - python-twisted-web (<< 16.0.0), -Description: meta package to use Twisted as HTTP server for CubicWeb - CubicWeb is a semantic web application framework. - . - This package includes dependencies to run a Twisted based HTTP server to serve - the adaptative web interface. - - Package: python-cubicweb-pyramid Architecture: all Depends: @@ -233,16 +218,6 @@ This is a transitional package. It can safely be removed. -Package: cubicweb-twisted -Architecture: all -Priority: extra -Section: oldlibs -Depends: - python-cubicweb-twisted, ${misc:Depends} -Description: transitional package - This is a transitional package. It can safely be removed. - - Package: cubicweb-web Architecture: all Priority: extra diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/admin/create-instance.rst --- a/doc/book/admin/create-instance.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/admin/create-instance.rst Fri May 24 16:29:14 2019 +0200 @@ -41,7 +41,7 @@ located in :file:`~/etc/cubicweb.d/myinstance/*`. To launch it, you just type :: - cubicweb-ctl start -D myinstance + cubicweb-ctl pyramid -D myinstance The option `-D` specifies the *debug mode* : the instance is not running in server mode and does not disconnect from the terminal, @@ -60,9 +60,9 @@ .. note:: - The output of `cubicweb-ctl start -D myinstance` can be + The output of `cubicweb-ctl pyramid -D myinstance` can be overwhelming. It is possible to reduce the log level with the - `--loglevel` parameter as in `cubicweb-ctl start -D myinstance -l + `--loglevel` parameter as in `cubicweb-ctl pyramid -D myinstance -l info` to filter out all logs under `info` gravity. upgrade diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/admin/cubicweb-ctl.rst --- a/doc/book/admin/cubicweb-ctl.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/admin/cubicweb-ctl.rst Fri May 24 16:29:14 2019 +0200 @@ -65,21 +65,19 @@ * ``db-init``, initializes the system database of an instance (schema, groups, users, workflows...) -Commands to control instances ------------------------------ +Run an instance +--------------- -* ``start``, starts one or more or all instances +To start an instance during development, use :: -of special interest:: + cubicweb-ctl pyramid [-D] [-l ] - start -D +without ``-D``, the instance will be start in the background, as a daemon. -will start in debug mode (under windows, starting without -D will not -work; you need instead to setup your instance as a service). +See :ref:`cubicweb-ctl_pyramid` for more details. -* ``stop``, stops one or more or all instances -* ``restart``, restarts one or more or all instances -* ``status``, returns the status of the instance(s) +In production, it is recommended to run CubicWeb through a WSGI server like +uWSGI or Gunicorn. See :mod:`cubicweb.pyramid` more details. Commands to maintain instances ------------------------------ diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/admin/ldap.rst --- a/doc/book/admin/ldap.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/admin/ldap.rst Fri May 24 16:29:14 2019 +0200 @@ -116,10 +116,9 @@ Other notes ----------- -* Cubicweb is able to start if ldap cannot be reached, even on - cubicweb-ctl start ... If some source ldap server cannot be used - while an instance is running, the corresponding users won't be - authenticated but their status will not change (e.g. they will not +* Cubicweb is able to start if ldap cannot be reached... If some source ldap + server cannot be used while an instance is running, the corresponding users + won't be authenticated but their status will not change (e.g. they will not be deactivated) * The user-base-dn is a key that helps cubicweb map CWUsers to LDAP diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/admin/setup-windows.rst --- a/doc/book/admin/setup-windows.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/admin/setup-windows.rst Fri May 24 16:29:14 2019 +0200 @@ -32,10 +32,6 @@ applications (including Eclipse + pydev, which is an arguably good IDE for Python under Windows). -* `Twisted `_ is an event-driven - networking engine - (`Download Twisted `_) - * `lxml `_ library (version >=2.2.1) allows working with XML and HTML (`Download lxml `_) diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/admin/setup.rst --- a/doc/book/admin/setup.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/admin/setup.rst Fri May 24 16:29:14 2019 +0200 @@ -67,9 +67,6 @@ # if you want pyramid, the recommended application server apt-get install pyramid-cubicweb - # or if you want twisted (considered deprecated) - apt-get install cubicweb-twisted - ``cubicweb`` installs the framework itself, allowing you to create new instances. ``cubicweb-dev`` installs the development environment allowing you to develop new cubes. @@ -137,8 +134,7 @@ A working compilation chain is needed to build the modules that include C extensions. If you really do not want to compile anything, installing `lxml `_, -`Twisted Web `_ and `libgecode -`_ will help. +and `libgecode `_ will help. For Debian, these minimal dependencies can be obtained by doing:: @@ -155,18 +151,13 @@ - setuptools http://www.lfd.uci.edu/~gohlke/pythonlibs/#setuptools - libxml-python http://www.lfd.uci.edu/~gohlke/pythonlibs/#libxml-python> - lxml http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml and -- twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted Make sure to choose the correct architecture and version of Python. Finally, install |cubicweb| and its dependencies, by running:: - # for pyramid, the recommended application server pip install cubicweb[pyramid] - # or for twisted, considered deprecated (used by "cubicweb-ctl") - pip install cubicweb[etwist] - Many other :ref:`cubes ` are available. A list is available at `PyPI `_ or at the `CubicWeb.org forge`_. diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/annexes/depends.rst --- a/doc/book/annexes/depends.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/annexes/depends.rst Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ * lxml - http://codespeak.net/lxml - http://pypi.python.org/pypi/lxml -* twisted - http://twistedmatrix.com/ - http://pypi.python.org/pypi/Twisted - * logilab-common - http://www.logilab.org/project/logilab-common - http://pypi.python.org/pypi/logilab-common/ diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/annexes/rql/language.rst --- a/doc/book/annexes/rql/language.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/annexes/rql/language.rst Fri May 24 16:29:14 2019 +0200 @@ -557,7 +557,7 @@ ~~~~~~~~~~~~~~~~~~~ Below is the list of aggregate and transformation functions that are supported -nativly by the framework. Notice that cubes may define additional functions. +natively by the framework. Notice that cubes may define additional functions. .. _RQLAggregateFunctions: diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devrepo/datamodel/baseschema.rst --- a/doc/book/devrepo/datamodel/baseschema.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devrepo/datamodel/baseschema.rst Fri May 24 16:29:14 2019 +0200 @@ -29,7 +29,6 @@ Other entity types ~~~~~~~~~~~~~~~~~~ -* _`CWCache`, cache entities used to improve performances * _`CWProperty`, used to configure the instance * _`EmailAddress`, email address, used by the system to send notifications diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devrepo/entityclasses/data-as-objects.rst --- a/doc/book/devrepo/entityclasses/data-as-objects.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devrepo/entityclasses/data-as-objects.rst Fri May 24 16:29:14 2019 +0200 @@ -113,7 +113,7 @@ .. sourcecode:: python - from cubes.OTHER_CUBE import entities + from cubicweb_OTHER_CUBE import entities class EntityExample(entities.EntityExample): diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devrepo/repo/hooks.rst --- a/doc/book/devrepo/repo/hooks.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devrepo/repo/hooks.rst Fri May 24 16:29:14 2019 +0200 @@ -19,7 +19,7 @@ age = Int(required=True) We would like to add a range constraint over a person's age. Let's write an hook -(supposing yams can not handle this nativly, which is wrong). It shall be placed +(supposing yams can not handle this natively, which is wrong). It shall be placed into `mycube/hooks.py`. If this file were to grow too much, we can easily have a `mycube/hooks/... package` containing hooks in various modules. @@ -44,7 +44,7 @@ In our example the base `__select__` is augmented with an `is_instance` selector matching the desired entity type. -The `events` tuple is used specify that our hook should be called before the +The `events` tuple is used to specify that our hook should be called before the entity is added or updated. Then in the hook's `__call__` method, we: @@ -53,7 +53,7 @@ * if so, check the value is in the range * if not, raise a validation error properly -Now Let's augment our schema with new `Company` entity type with some relation to +Now let's augment our schema with a new `Company` entity type with some relation to `Person` (in 'mycube/schema.py'). .. sourcecode:: python @@ -99,7 +99,7 @@ from cubicweb.server.hook import Hook, DataOperationMixIn, Operation, match_rtype - def check_cycle(self, session, eid, rtype, role='subject'): + def check_cycle(session, eid, rtype, role='subject'): parents = set([eid]) parent = session.entity_from_eid(eid) while parent.related(rtype, role): @@ -135,8 +135,6 @@ .. sourcecode:: python - from cubicweb.server.hook import set_operation - class CheckSubsidiaryCycleHook(Hook): __regid__ = 'check_no_subsidiary_cycle' events = ('after_add_relation',) @@ -152,11 +150,11 @@ check_cycle(self.session, eid, self.rtype) -Here, we call :func:`set_operation` so that we will simply accumulate eids of +Here, we call :func:`add_data` so that we will simply accumulate eids of entities to check at the end in a single `CheckSubsidiaryCycleOp` -operation. Value are stored in a set associated to the -'subsidiary_cycle_detection' transaction data key. The set initialization and -operation creation are handled nicely by :func:`set_operation`. +operation. Values are stored in a set associated to the +'check_no_subsidiary_cycle' transaction data key. The set initialization and +operation creation are handled nicely by :func:`add_data`. A more realistic example can be found in the advanced tutorial chapter :ref:`adv_tuto_security_propagation`. diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devweb/edition/form.rst --- a/doc/book/devweb/edition/form.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devweb/edition/form.rst Fri May 24 16:29:14 2019 +0200 @@ -35,7 +35,7 @@ {'base': [, ], 'changestate': [, - ], + ], 'composite': [, ], 'deleteconf': [], diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devweb/internationalization.rst --- a/doc/book/devweb/internationalization.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devweb/internationalization.rst Fri May 24 16:29:14 2019 +0200 @@ -58,23 +58,23 @@ retrieve the proper translation of translation strings in the requested language. -Finally you can also use the `__` attribute of request object to get a +Finally you can also use the `__` (two underscores) attribute of request object to get a translation for a string *which should not itself added to the catalog*, usually in case where the actual msgid is created by string interpolation :: self._cw.__('This %s' % etype) -In this example ._cw.__` is used instead of ._cw._` so we don't have 'This %s' in +In this example `._cw.__` is used instead of `._cw._` so we don't have 'This %s' in messages catalogs. Translations in cubicweb-tal template can also be done with TAL tags `i18n:content` and `i18n:replace`. -If you need to add messages on top of those that can be found in the source, -you can create a file named `i18n/static-messages.pot`. +If you need to mark other messages as translatable, +you can create a file named `i18n/static-messages.pot`, see for example :ref:`translate-application-cube`. You could put there messages not found in the python sources or -overrides for some messages of used cubes. +overrides some messages that are in cubes used in the dependencies. Generated string ```````````````` @@ -149,7 +149,7 @@ To update the translation catalogs you need to do: 1. `cubicweb-ctl i18ncube ` -2. Edit the /i18n/xxx.po files and add missing translations (empty `msgstr`) +2. Edit the `/i18n/xxx.po` files and add missing translations (those with an empty `msgstr`) 3. `hg ci -m "updated i18n catalogs"` 4. `cubicweb-ctl i18ninstance ` @@ -280,10 +280,12 @@ entity.dc_type(), eschema.display_name(req) or display_name(etype, req, form, context) methods/function calls will use them. -It is also possible to explicitly use the with _cw.pgettext(context, -msgid). +It is also possible to explicitly use a context with `_cw.pgettext(context, +msgid)`. +.. _translate-application-cube: + Specialize translation for an application cube `````````````````````````````````````````````` diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devweb/publisher.rst --- a/doc/book/devweb/publisher.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devweb/publisher.rst Fri May 24 16:29:14 2019 +0200 @@ -7,11 +7,7 @@ The story begins with the ``CubicWebPublisher.main_publish`` method. We do not get upper in the bootstrap process because it is -dependant on the used HTTP library. With `twisted`_ however, -``cubicweb.etwist.server.CubicWebRootResource.render_request`` is the -real entry point. - -.. _`twisted`: http://twistedmatrix.com/trac/ +dependant on the used HTTP library. What main_publish does: diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/devweb/request.rst --- a/doc/book/devweb/request.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/devweb/request.rst Fri May 24 16:29:14 2019 +0200 @@ -107,10 +107,9 @@ *while a request is executed* Please note that this class is abstract and that a concrete implementation -will be provided by the *frontend* web used (in particular *twisted* as of -today). For the views or others that are executed on the server side, -most of the interface of `Request` is defined in the session associated -to the client. +will be provided by the *frontend* web used. For the views or others that are +executed on the server side, most of the interface of `Request` is defined in +the session associated to the client. API ``` diff -r ba5231e1aa45 -r 32ee89340e59 doc/book/pyramid/ctl.rst --- a/doc/book/pyramid/ctl.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/book/pyramid/ctl.rst Fri May 24 16:29:14 2019 +0200 @@ -8,10 +8,6 @@ The 'pyramid' command is a replacement for the 'start' command of :ref:`cubicweb-ctl`. It provides the same options and a few other ones. -.. note:: - - The 'pyramid' command is provided by the ``pyramid`` cube. - Options ------- diff -r ba5231e1aa45 -r 32ee89340e59 doc/changes/3.25.rst --- a/doc/changes/3.25.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/changes/3.25.rst Fri May 24 16:29:14 2019 +0200 @@ -77,6 +77,8 @@ (4516c3956d46). * The `next_tabindex` method of request class has been removed (011730a4af73). + This include the removal of `settabindex` from the `FieldWidget` class init + method. * The `cubicweb.hook.logstats.start` hook was dropped because it's looping task would not be run in a web instance (see first point about repository diff -r ba5231e1aa45 -r 32ee89340e59 doc/changes/3.27.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/changes/3.27.rst Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,46 @@ +3.27 (not yet released) +======================= + +New features +------------ + +* Tests can now be run concurrently across multiple processes. You can use + `pytest-xdist`_ for that. For tests using `PostgresApptestConfiguration` you + should be aware that `startpgcluster()` can't run concurrently. Workaround is + to call pytest with ``--dist=loadfile`` to use a single test process per test + module or use an existing database cluster and set ``db-host`` and + ``db-port`` of ``devtools.DEFAULT_PSQL_SOURCES['system']`` accordingly. + +.. _pytest-xdist: https://github.com/pytest-dev/pytest-xdist + +* on `cubicweb-ctl create` and `cubicweb-ctl pyramid`, if it doesn't already + exist in the instance directory, the `pyramid.ini` file will be generated + with the needed secrets. + +Backwards incompatible changes +------------------------------ + +* Standardization on the way to launch a cubicweb instance, from now on the + only way to do that will be the used the ``pyramid`` command. Therefore: + + * ``cubicweb-ctl`` commands "start", "stop", "restart", "reload" and "status" + have been removed because they relied on the Twisted web server backend that + is no longer maintained nor working with Python 3. + + * Twisted web server support has been removed. + + * ``cubicweb-ctl wsgi`` has also been removed. + +* Support for legacy cubes (in the 'cubes' python namespace) has been dropped. + Use of environment variables CW_CUBES_PATH and CUBES_DIR is removed. + +* Python 2 support has been dropped. + +* Exceptions in notification hooks aren't catched-all anymore during tests so + one can expect tests that seem to pass (but were actually silently failing) + to fail now. + +Deprecated code drops +--------------------- + +Most code deprecated until version 3.25 has been dropped. diff -r ba5231e1aa45 -r 32ee89340e59 doc/dev/features_list.rst --- a/doc/dev/features_list.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/dev/features_list.rst Fri May 24 16:29:14 2019 +0200 @@ -171,7 +171,6 @@ | +--------------------------------------------------------+----+----+ | | mime type based conversion | 2 | 0 | | +--------------------------------------------------------+----+----+ - | | CWCache | 1 | 0 | +-----------+--------------------------------------------------------+----+----+ diff -r ba5231e1aa45 -r 32ee89340e59 doc/tools/mode_plan.py --- a/doc/tools/mode_plan.py Tue May 21 10:50:08 2019 +0200 +++ b/doc/tools/mode_plan.py Fri May 24 16:29:14 2019 +0200 @@ -23,8 +23,6 @@ rename A010-joe.en.txt to A030-joe.en.txt accept [y/N]? """ -from __future__ import print_function - def ren(a,b): names = glob.glob('%s*'%a) diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/advanced/part01_create-cube.rst --- a/doc/tutorials/advanced/part01_create-cube.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/advanced/part01_create-cube.rst Fri May 24 16:29:14 2019 +0200 @@ -10,9 +10,9 @@ Fisrt I need a python virtual environment with cubicweb:: - virtualenv python-2.7.5_cubicweb - . /python-2.7.5_cubicweb/bin/activate - pip install cubicweb[etwist] + python3 -m venv venv + . venv/bin/activate + pip install cubicweb[pyramid] Step 2: creating a new cube for my web site @@ -20,9 +20,9 @@ One note about my development environment: I wanted to use the packaged version of CubicWeb and cubes while keeping my cube in the current -directory, let's say `~src/cubes`:: +directory, let's say `~/src/cubes`:: - cd ~src/cubes + cd ~/src/cubes CW_MODE=user I can now create the cube which will hold custom code for this web @@ -173,7 +173,7 @@ Once the instance and database are fully initialized, run :: - cubicweb-ctl start -D sytweb_instance + cubicweb-ctl pyramid -D sytweb_instance to start the instance, check you can connect on it, etc... then go on http://localhost:8080 (or with another port if you've modified it) diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/advanced/part02_security.rst --- a/doc/tutorials/advanced/part02_security.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/advanced/part02_security.rst Fri May 24 16:29:14 2019 +0200 @@ -137,12 +137,12 @@ 'delete': ('managers', 'owners'), } - from cubes.folder.schema import Folder - from cubes.file.schema import File - from cubes.comment.schema import Comment - from cubes.person.schema import Person - from cubes.zone.schema import Zone - from cubes.tag.schema import Tag + from cubicweb_folder.schema import Folder + from cubicweb_file.schema import File + from cubicweb_comment.schema import Comment + from cubicweb_person.schema import Person + from cubicweb_zone.schema import Zone + from cubicweb_tag.schema import Tag Folder.__permissions__ = VISIBILITY_PERMISSIONS File.__permissions__ = VISIBILITY_PERMISSIONS diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/advanced/part04_ui-base.rst --- a/doc/tutorials/advanced/part04_ui-base.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/advanced/part04_ui-base.rst Fri May 24 16:29:14 2019 +0200 @@ -174,7 +174,7 @@ .. sourcecode:: python - from cubes.folder import entities as folder + from cubicweb_folder import entities as folder class FolderITreeAdapter(folder.FolderITreeAdapter): @@ -327,7 +327,7 @@ To see if everything is ok on my test instance, I do: :: $ cubicweb-ctl i18ninstance sytweb_instance - $ cubicweb-ctl start -D sytweb_instance + $ cubicweb-ctl pyramid -D sytweb_instance The first command compile i18n catalogs (e.g. generates '.mo' files) for my test instance. The second command start it in debug mode, so I can open my browser and diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/advanced/part05_ui-advanced.rst --- a/doc/tutorials/advanced/part05_ui-advanced.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/advanced/part05_ui-advanced.rst Fri May 24 16:29:14 2019 +0200 @@ -196,8 +196,8 @@ from cubicweb.predicates import none_rset from cubicweb.web.views import bookmark - from cubes.zone import views as zone - from cubes.tag import views as tag + from cubicweb_zone import views as zone + from cubicweb_tag import views as tag # change bookmarks box selector so it's only displayed on startup views diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/base/blog-in-five-minutes.rst --- a/doc/tutorials/base/blog-in-five-minutes.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/base/blog-in-five-minutes.rst Fri May 24 16:29:14 2019 +0200 @@ -8,7 +8,7 @@ For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`):: - cubicweb, cubicweb-dev, cubicweb-twisted, cubicweb-blog + cubicweb, cubicweb-dev, cubicweb-pyramid, cubicweb-blog Windows or Mac OS X users must install |cubicweb| from source (see :ref:`SourceInstallation` and :ref:`WindowsInstallation`). @@ -17,7 +17,7 @@ virtualenv venv source venv/bin/activate - pip install cubicweb[etwist] cubicweb-dev cubicweb-blog + pip install cubicweb[pyramid] cubicweb-dev cubicweb-blog Then create and initialize your instance:: @@ -38,19 +38,16 @@ choose `sqlite` when asked for which database driver to use, since it has a much simple setup (no database server needed). +Then, you need to setup the CubicWeb Pyramid interface, as document at +:ref:`pyramid_settings`. + One the process is completed (including database initialisation), you can start your instance by using: :: - cubicweb-ctl start -D myblog + cubicweb-ctl pyramid -D myblog The `-D` option activates the debugging mode. Removing it will launch the instance -as a daemon in the background, and ``cubicweb-ctl stop myblog`` will stop -it in that case. - -.. Note:: - - If you get a traceback when going on the web interface make sure your - version of twisted is **inferior** to 17. +as a daemon in the background. .. _AboutFileSystemPermissions: diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/base/customizing-the-application.rst --- a/doc/tutorials/base/customizing-the-application.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/base/customizing-the-application.rst Fri May 24 16:29:14 2019 +0200 @@ -26,20 +26,8 @@ cubicweb-ctl newcube myblog -This will create in the cubes directory (:file:`/path/to/grshell/cubes` for source -installation, :file:`/usr/share/cubicweb/cubes` for Debian packages installation) -a directory named :file:`blog` reflecting the structure described in -:ref:`cubelayout`. - -For packages installation, you can still create new cubes in your home directory -using the following configuration. Let's say you want to develop your new cubes -in `~src/cubes`, then set the following environment variables: :: - - CW_CUBES_PATH=~/src/cubes - -and then create your new cube using: :: - - cubicweb-ctl newcube --directory=~/src/cubes myblog +This will create a a directory named :file:`cubicweb-myblog` reflecting the +structure described in :ref:`cubelayout`. .. Note:: @@ -58,7 +46,7 @@ .. sourcecode:: python - __depends__ = {'cubicweb': '>= 3.10.7', + __depends__ = {'cubicweb': '>= 3.24.0', 'cubicweb-blog': None} where the ``None`` means we do not depends on a particular version of the cube. @@ -136,7 +124,7 @@ cubicweb-ctl stop myblog # or Ctrl-C in the terminal running the server in debug mode cubicweb-ctl delete myblog cubicweb-ctl create myblog myblog - cubicweb-ctl start -D myblog + cubicweb-ctl pyramid -D myblog Another way is to add our cube to the instance using the cubicweb-ctl shell facility. It's a python shell connected to the instance with some special @@ -151,7 +139,7 @@ type "exit" or Ctrl-D to quit the shell and resume operation >>> add_cube('myblog') >>> - $ cubicweb-ctl start -D myblog + $ cubicweb-ctl pyramid -D myblog The `add_cube` command is enough since it automatically updates our application to the cube's schema. There are plenty of other migration @@ -225,7 +213,7 @@ cubicweb-ctl i18ncube myblog # build/update cube's message catalogs # then add translation into .po file into the cube's i18n directory cubicweb-ctl i18ninstance myblog # recompile instance's message catalogs - cubicweb-ctl restart -D myblog # instance has to be restarted to consider new catalogs + # instance has to be restarted to consider new catalogs You'll then be able to redefine each of them according to your needs and preferences. So let's see how to do such thing. diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/dataimport/diseasome_import.py --- a/doc/tutorials/dataimport/diseasome_import.py Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/dataimport/diseasome_import.py Fri May 24 16:29:14 2019 +0200 @@ -27,7 +27,7 @@ # CubicWeb imports import cubicweb.dataimport as cwdi -from cubes.dataio import dataimport as mcwdi +from cubicweb_dataio import dataimport as mcwdi # Diseasome parser import import diseasome_parser as parser diff -r ba5231e1aa45 -r 32ee89340e59 doc/tutorials/dataimport/index.rst --- a/doc/tutorials/dataimport/index.rst Tue May 21 10:50:08 2019 +0200 +++ b/doc/tutorials/dataimport/index.rst Fri May 24 16:29:14 2019 +0200 @@ -354,7 +354,7 @@ ``dataimport`` module, then initialize the store by giving it the ``session`` parameter:: - from cubes.dataio import dataimport as mcwdi + from cubicweb_dataio import dataimport as mcwdi ... store = mcwdi.MassiveObjectStore(session) @@ -441,7 +441,7 @@ store = cwdi.SQLGenObjectStore(session) where ``cwdi`` is the imported ``cubicweb.dataimport`` or - ``cubes.dataio.dataimport``. + ``cubicweb_dataio.dataimport``. #. calls the diseasome parser, that is, the ``entities_from_rdf`` function in the ``diseasome_parser`` module and iterates on its result, in a line such as:: diff -r ba5231e1aa45 -r 32ee89340e59 flake8-ok-files.txt --- a/flake8-ok-files.txt Tue May 21 10:50:08 2019 +0200 +++ b/flake8-ok-files.txt Fri May 24 16:29:14 2019 +0200 @@ -1,5 +1,8 @@ cubicweb/__init__.py cubicweb/__main__.py +cubicweb/_exceptions.py +cubicweb/cwctl.py +cubicweb/cwvreg.py cubicweb/crypto.py cubicweb/dataimport/csv.py cubicweb/dataimport/importer.py @@ -10,9 +13,9 @@ cubicweb/dataimport/test/test_csv.py cubicweb/dataimport/test/test_pgstore.py cubicweb/dataimport/test/test_massive_store.py -cubicweb/dataimport/test/test_sqlgenstore.py cubicweb/dataimport/test/test_stores.py cubicweb/dataimport/test/unittest_importer.py +cubicweb/devtools/httptest.py cubicweb/devtools/test/data/cubes/i18ntestcube/__init__.py cubicweb/devtools/test/data/cubes/i18ntestcube/views.py cubicweb/devtools/test/data/cubes/__init__.py @@ -25,9 +28,6 @@ cubicweb/entities/adapters.py cubicweb/entities/authobjs.py cubicweb/entities/test/unittest_base.py -cubicweb/etwist/__init__.py -cubicweb/etwist/request.py -cubicweb/etwist/service.py cubicweb/ext/__init__.py cubicweb/ext/test/unittest_rest.py cubicweb/hooks/synccomputed.py @@ -109,10 +109,12 @@ cubicweb/test/unittest_rset.py cubicweb/test/unittest_rtags.py cubicweb/test/unittest_schema.py +cubicweb/test/unittest_statsd.py cubicweb/test/unittest_toolsutils.py cubicweb/test/unittest_wfutils.py cubicweb/toolsutils.py cubicweb/web/application.py +cubicweb/web/box.py cubicweb/web/formwidgets.py cubicweb/web/test/data/entities.py cubicweb/web/test/unittest_application.py @@ -131,6 +133,7 @@ cubicweb/web/views/wdoc.py cubicweb/web/views/workflow.py cubicweb/web/views/uicfg.py +cubicweb/web/webconfig.py cubicweb/web/webctl.py cubicweb/xy.py cubicweb/pyramid/auth.py @@ -150,6 +153,7 @@ cubicweb/pyramid/test/test_bw_request.py cubicweb/pyramid/test/test_config.py cubicweb/pyramid/test/test_core.py +cubicweb/pyramid/test/test_hooks.py cubicweb/pyramid/test/test_login.py cubicweb/pyramid/test/test_rest_api.py cubicweb/pyramid/test/test_tools.py diff -r ba5231e1aa45 -r 32ee89340e59 requirements/dev.txt --- a/requirements/dev.txt Tue May 21 10:50:08 2019 +0200 +++ b/requirements/dev.txt Fri May 24 16:29:14 2019 +0200 @@ -1,1 +1,2 @@ pytest +pytest-subtests diff -r ba5231e1aa45 -r 32ee89340e59 requirements/test-misc.txt --- a/requirements/test-misc.txt Tue May 21 10:50:08 2019 +0200 +++ b/requirements/test-misc.txt Fri May 24 16:29:14 2019 +0200 @@ -1,19 +1,13 @@ ### Requirements for tests in various cubicweb/**/test directories. ### ## shared by several test folders -cubicweb-card == 0.5.8 docutils -Twisted < 16.0.0 webtest ## cubicweb/test Pygments -pycrypto -mock +pycryptodome #fyzz XXX pip install fails -cubicweb-file == 1.18.0 -cubicweb-localperms == 0.3.2 -cubicweb-tag == 1.8.3 ## cubicweb/devtools/test flake8 @@ -29,4 +23,3 @@ repoze.lru ## cubicweb/sobject/test -cubicweb-comment == 1.12.2 diff -r ba5231e1aa45 -r 32ee89340e59 requirements/test-server.txt --- a/requirements/test-server.txt Tue May 21 10:50:08 2019 +0200 +++ b/requirements/test-server.txt Fri May 24 16:29:14 2019 +0200 @@ -1,9 +1,2 @@ -mock psycopg2-binary ldap3 < 2 -cubicweb-basket -cubicweb-card -cubicweb-comment -cubicweb-file >= 2.2.2 -cubicweb-localperms -cubicweb-tag diff -r ba5231e1aa45 -r 32ee89340e59 requirements/test-web.txt --- a/requirements/test-web.txt Tue May 21 10:50:08 2019 +0200 +++ b/requirements/test-web.txt Fri May 24 16:29:14 2019 +0200 @@ -1,7 +1,3 @@ docutils -Twisted < 16.0.0 requests webtest -cubicweb-blog -cubicweb-file >= 2.0.0 -cubicweb-tag diff -r ba5231e1aa45 -r 32ee89340e59 setup.py --- a/setup.py Tue May 21 10:50:08 2019 +0200 +++ b/setup.py Fri May 24 16:29:14 2019 +0200 @@ -63,17 +63,16 @@ package_data=package_data, include_package_data=True, install_requires=[ - 'six >= 1.4.0', 'logilab-common >= 1.4.0', 'logilab-mtconverter >= 0.8.0', 'rql >= 0.34.0', 'yams >= 0.45.0', 'lxml', 'logilab-database >= 1.15.0', - 'passlib', + 'passlib >= 1.7', 'pytz', 'Markdown', - 'unittest2 >= 0.7.0', + 'filelock', ], entry_points={ 'console_scripts': [ @@ -88,10 +87,7 @@ 'Pillow', ], 'crypto': [ - 'pycrypto', - ], - 'etwist': [ - 'Twisted < 16.0.0', + 'pycryptodome', ], 'ext': [ 'docutils >= 0.6', diff -r ba5231e1aa45 -r 32ee89340e59 tox.ini --- a/tox.ini Tue May 21 10:50:08 2019 +0200 +++ b/tox.ini Fri May 24 16:29:14 2019 +0200 @@ -1,24 +1,22 @@ [tox] envlist = check-manifest,flake8, - py{27,3}-{server,web,misc} + py3-{server,web,misc} [testenv] +basepython=python3 deps = -r{toxinidir}/requirements/dev.txt - py27: backports.tempfile misc: -r{toxinidir}/requirements/test-misc.txt server: -r{toxinidir}/requirements/test-server.txt web: -r{toxinidir}/requirements/test-web.txt commands = misc: {envpython} -m pip install --upgrade --no-deps --quiet git+git://github.com/logilab/yapps@master#egg=yapps misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/test {toxinidir}/cubicweb/dataimport/test {toxinidir}/cubicweb/devtools/test {toxinidir}/cubicweb/entities/test {toxinidir}/cubicweb/ext/test {toxinidir}/cubicweb/hooks/test {toxinidir}/cubicweb/sobjects/test {toxinidir}/cubicweb/wsgi/test {toxinidir}/cubicweb/pyramid/test - py27-misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/etwist/test server: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/server/test web: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/web/test [testenv:flake8] -basepython=python3 skip_install = true deps = flake8 >= 3.6 @@ -43,7 +41,7 @@ {envpython} -m check_manifest {toxinidir} \ # ignore symlinks that are not recognized by check-manifest, see # https://github.com/mgedmin/check-manifest/issues/69 - --ignore cubicweb/devtools/test/data/cubes/i18ntestcube*,cubicweb/test/data/legacy_cubes* + --ignore cubicweb/devtools/test/data/cubes/i18ntestcube*,cubicweb/server/test/data-migractions/cubicweb_*,cubicweb/server/test/data-migractions/migratedapp/cubicweb_*,cubicweb/sobjects/test/data/cubicweb_*,cubicweb/test/data-rewrite/cubicweb_* [pytest] python_files = *test_*.py
') - jscall = text_type(js.ajaxBoxShowSelector( + jscall = str(js.ajaxBoxShowSelector( self.__regid__, entity.eid, self.fname_vocabulary, self.fname_validate, self.added_msg and _(self.added_msg), _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]), @@ -707,8 +702,7 @@ # old contextual components, deprecated ######################################## -@add_metaclass(class_deprecated) -class EntityVComponent(Component): +class EntityVComponent(Component, metaclass=class_deprecated): """abstract base class for additinal components displayed in content headers and footer according to: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/controller.py --- a/cubicweb/web/controller.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/controller.py Fri May 24 16:29:14 2019 +0200 @@ -17,13 +17,8 @@ # with CubicWeb. If not, see . """abstract controller classe for CubicWeb web client""" - - -from six import PY2 - from logilab.mtconverter import xml_escape from logilab.common.registry import yes -from logilab.common.deprecation import deprecated from cubicweb.appobject import AppObject from cubicweb.mail import format_mail @@ -89,8 +84,6 @@ rql = req.form.get('rql') if rql: req.ensure_ro_rql(rql) - if PY2 and not isinstance(rql, unicode): - rql = unicode(rql, req.encoding) pp = req.vreg['components'].select_or_none('magicsearch', req) if pp is not None: return pp.process_query(rql) @@ -106,12 +99,6 @@ if not self._edited_entity: self._edited_entity = entity - @deprecated('[3.18] call view.set_http_cache_headers then ' - '.is_client_cache_valid() method and return instead') - def validate_cache(self, view): - view.set_http_cache_headers() - self._cw.validate_cache() - def sendmail(self, recipient, subject, body): senderemail = self._cw.user.cw_adapt_to('IEmailable').get_email() msg = format_mail({'email' : senderemail, diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/cors.py --- a/cubicweb/web/cors.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/cors.py Fri May 24 16:29:14 2019 +0200 @@ -29,7 +29,7 @@ """ -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from cubicweb.web import LOGGER info = LOGGER.info diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/data/cubicweb.ajax.js --- a/cubicweb/web/data/cubicweb.ajax.js Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/data/cubicweb.ajax.js Fri May 24 16:29:14 2019 +0200 @@ -473,19 +473,8 @@ } // else: rebuild full url by fetching cubicweb:rql, cubicweb:vid, etc. var rql = $fragment.attr('cubicweb:rql'); - var items = $fragment.attr('cubicweb:vid').split('&'); - var vid = items[0]; + var vid = $fragment.attr('cubicweb:vid'); var extraparams = {}; - // case where vid='myvid¶m1=val1¶m2=val2': this is a deprecated abuse-case - if (items.length > 1) { - cw.log("[3.5] you're using extraargs in cubicweb:vid " + - "attribute, this is deprecated, consider using " + - "loadurl instead"); - for (var j = 1; j < items.length; j++) { - var keyvalue = items[j].split('='); - extraparams[keyvalue[0]] = keyvalue[1]; - } - } var actrql = $fragment.attr('cubicweb:actualrql'); if (actrql) { extraparams['actualrql'] = actrql; @@ -679,23 +668,6 @@ jQuery('#lazy-' + divid).trigger('load_' + divid); } -/* DEPRECATED *****************************************************************/ - -// still used in cwo and keyword cubes at least -reloadComponent = cw.utils.deprecatedFunction( - '[3.9] reloadComponent() is deprecated, use loadxhtml instead', - function(compid, rql, registry, nodeid, extraargs) { - registry = registry || 'components'; - rql = rql || ''; - nodeid = nodeid || (compid + 'Component'); - extraargs = extraargs || {}; - var node = cw.jqNode(nodeid); - return node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('component', null, compid, - rql, registry, extraargs)); - } -); - - function remoteExec(fname /* ... */) { setProgressCursor(); var props = { diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/data/cubicweb.compat.js --- a/cubicweb/web/data/cubicweb.compat.js Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/data/cubicweb.compat.js Fri May 24 16:29:14 2019 +0200 @@ -18,12 +18,3 @@ } return result; } - - -// skm cube still uses this -getNodeAttribute = cw.utils.deprecatedFunction( - '[3.9] getNodeAttribute(node, attr) is deprecated, use $(node).attr(attr)', - function(node, attribute) { - return $(node).attr(attribute); - } -); diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/data/cubicweb.htmlhelpers.js --- a/cubicweb/web/data/cubicweb.htmlhelpers.js Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/data/cubicweb.htmlhelpers.js Fri May 24 16:29:14 2019 +0200 @@ -10,17 +10,6 @@ /** - * .. function:: baseuri() - * - * returns the document's baseURI. - */ -baseuri = cw.utils.deprecatedFunction( - "[3.20] baseuri() is deprecated, use BASE_URL instead", - function () { - return BASE_URL; - }); - -/** * .. function:: setProgressCursor() * * set body's cursor to 'progress' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/data/cubicweb.js --- a/cubicweb/web/data/cubicweb.js Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/data/cubicweb.js Fri May 24 16:29:14 2019 +0200 @@ -426,15 +426,6 @@ // backward compat CubicWeb = cw; -jQuery.extend(cw, { - require: cw.utils.deprecatedFunction( - '[3.9] CubicWeb.require() is not used anymore', - function(module) {}), - provide: cw.utils.deprecatedFunction( - '[3.9] CubicWeb.provide() is not used anymore', - function(module) {}) -}); - jQuery(document).ready(function() { $(cw).trigger('server-response', [false, document]); }); diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/facet.py --- a/cubicweb/web/facet.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/facet.py Fri May 24 16:29:14 2019 +0200 @@ -53,17 +53,13 @@ from cubicweb import _ from functools import reduce -from warnings import warn from copy import deepcopy from datetime import datetime, timedelta -from six import text_type, string_types - from logilab.mtconverter import xml_escape from logilab.common.graph import has_path from logilab.common.decorators import cached, cachedproperty from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime -from logilab.common.deprecation import deprecated from logilab.common.registry import yes from rql import nodes, utils @@ -89,12 +85,6 @@ return req.vreg['facets'].object_by_id(facetid, req, select=select, filtered_variable=filtered_variable) -@deprecated('[3.13] filter_hiddens moved to cubicweb.web.views.facets with ' - 'slightly modified prototype') -def filter_hiddens(w, baserql, **kwargs): - from cubicweb.web.views.facets import filter_hiddens - return filter_hiddens(w, baserql, wdgs=kwargs.pop('facets'), **kwargs) - ## rqlst manipulation functions used by facets ################################ @@ -164,15 +154,6 @@ # global tree config: DISTINCT, LIMIT, OFFSET select.set_distinct(True) -@deprecated('[3.13] use init_facets instead') -def prepare_facets_rqlst(rqlst, args=None): - assert len(rqlst.children) == 1, 'FIXME: union not yet supported' - select = rqlst.children[0] - filtered_variable = get_filtered_variable(select) - baserql = select.as_string(args) - prepare_select(select, filtered_variable) - return filtered_variable, baserql - def prepare_vocabulary_select(select, filtered_variable, rtype, role, select_target_entity=True): """prepare a syntax tree to generate a filter vocabulary rql using the given @@ -370,11 +351,6 @@ return var -_prepare_vocabulary_rqlst = deprecated('[3.13] renamed prepare_vocabulary_select')( - prepare_vocabulary_select) -_cleanup_rqlst = deprecated('[3.13] renamed to cleanup_select')(cleanup_select) - - ## base facet classes ########################################################## class AbstractFacet(AppObject): @@ -479,11 +455,6 @@ def wdgclass(self): raise NotImplementedError - @property - @deprecated('[3.13] renamed .select') - def rqlst(self): - return self.select - class VocabularyFacet(AbstractFacet): """This abstract class extend :class:`AbstractFacet` to use the @@ -672,7 +643,7 @@ insert_attr_select_relation( select, self.filtered_variable, self.rtype, self.role, self.target_attr, select_target_entity=False) - values = [text_type(x) for x, in self.rqlexec(select.as_string())] + values = [str(x) for x, in self.rqlexec(select.as_string())] except Exception: self.exception('while computing values for %s', self) return [] @@ -723,7 +694,7 @@ if self.i18nable: tr = self._cw._ else: - tr = text_type + tr = str if self.rql_sort: values = [(tr(label), eid) for eid, label in rset] else: @@ -743,20 +714,11 @@ # internal utilities ####################################################### - @cached - def _support_and_compat(self): - support = self.support_and - if callable(support): - warn('[3.13] %s.support_and is now a property' % self.__class__, - DeprecationWarning) - support = support() - return support - def value_restriction(self, restrvar, rel, value): # XXX handle rel is None case in RQLPathFacet? if self.restr_attr != 'eid': self.select.set_distinct(True) - if isinstance(value, string_types): + if isinstance(value, str): # only one value selected if value: self.select.add_constant_restriction( @@ -766,7 +728,7 @@ rel.parent.replace(rel, nodes.Not(rel)) elif self.operator == 'OR': # set_distinct only if rtype cardinality is > 1 - if self._support_and_compat(): + if self.support_and: self.select.set_distinct(True) # multiple ORed values: using IN is fine if '' in value: @@ -920,7 +882,7 @@ if self.i18nable: tr = self._cw._ else: - tr = text_type + tr = str if self.rql_sort: return [(tr(value), value) for value, in rset] values = [(tr(value), value) for value, in rset] @@ -1073,7 +1035,7 @@ assert self.path and isinstance(self.path, (list, tuple)), \ 'path should be a list of 3-uples, not %s' % self.path for part in self.path: - if isinstance(part, string_types): + if isinstance(part, str): part = part.split() assert len(part) == 3, \ 'path should be a list of 3-uples, not %s' % part @@ -1126,7 +1088,7 @@ cleanup_select(select, self.filtered_variable) varmap, restrvar = self.add_path_to_select(skiplabel=True) select.append_selected(nodes.VariableRef(restrvar)) - values = [text_type(x) for x, in self.rqlexec(select.as_string())] + values = [str(x) for x, in self.rqlexec(select.as_string())] except Exception: self.exception('while computing values for %s', self) return [] @@ -1149,7 +1111,7 @@ varmap = {'X': self.filtered_variable} actual_filter_variable = None for part in self.path: - if isinstance(part, string_types): + if isinstance(part, str): part = part.split() subject, rtype, object = part if skiplabel and object == self.label_variable: @@ -1253,7 +1215,7 @@ rset = self._range_rset() if rset: minv, maxv = rset[0] - return [(text_type(minv), minv), (text_type(maxv), maxv)] + return [(str(minv), minv), (str(maxv), maxv)] return [] def possible_values(self): @@ -1272,7 +1234,7 @@ def formatvalue(self, value): """format `value` before in order to insert it in the RQL query""" - return text_type(value) + return str(value) def infvalue(self, min=False): if min: @@ -1373,7 +1335,7 @@ # *list* (see rqlexec implementation) if rset: minv, maxv = rset[0] - return [(text_type(minv), minv), (text_type(maxv), maxv)] + return [(str(minv), minv), (str(maxv), maxv)] return [] @@ -1392,7 +1354,7 @@ skiplabel=True, skipattrfilter=True) restrel = None for part in self.path: - if isinstance(part, string_types): + if isinstance(part, str): part = part.split() subject, rtype, object = part if object == self.filter_variable: @@ -1516,7 +1478,7 @@ if not val or val & mask]) def possible_values(self): - return [text_type(val) for label, val in self.vocabulary()] + return [str(val) for label, val in self.vocabulary()] ## html widets ################################################################ @@ -1542,7 +1504,7 @@ def height(self): """ title, optional and/or dropdown, len(items) or upper limit """ return (1.5 + # title + small magic constant - int(self.facet._support_and_compat() + + int(self.facet.support_and + min(len(self.items), self.css_overflow_limit))) @property @@ -1562,14 +1524,14 @@ cssclass += ' hideFacetBody' w(u'
%s
\n' % (cssclass, xml_escape(self.facet.__regid__), title)) - if self.facet._support_and_compat(): + if self.facet.support_and: self._render_and_or(w) cssclass = 'facetBody vocabularyFacet' if not self.facet.start_unfolded: cssclass += ' hidden' overflow = self.overflows if overflow: - if self.facet._support_and_compat(): + if self.facet.support_and: cssclass += ' vocabularyFacetBodyWithLogicalSelector' else: cssclass += ' vocabularyFacetBody' @@ -1595,7 +1557,7 @@ if selected: cssclass += ' facetValueSelected' w(u'
\n' - % (cssclass, xml_escape(text_type(value)))) + % (cssclass, xml_escape(str(value)))) # If it is overflowed one must add padding to compensate for the vertical # scrollbar; given current css values, 4 blanks work perfectly ... padding = u' ' * self.scrollbar_padding_factor if overflow else u'' @@ -1754,7 +1716,7 @@ imgsrc = self._cw.data_url(self.unselected_img) imgalt = self._cw._('not selected') w(u'
\n' - % (cssclass, xml_escape(text_type(self.value)))) + % (cssclass, xml_escape(str(self.value)))) w(u'
') w(u'%s ' % (imgsrc, imgalt)) w(u'' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/form.py --- a/cubicweb/web/form.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/form.py Fri May 24 16:29:14 2019 +0200 @@ -17,13 +17,7 @@ # with CubicWeb. If not, see . """abstract form classes for CubicWeb web client""" - -from warnings import warn - -from six import add_metaclass - from logilab.common.decorators import iclassmethod -from logilab.common.deprecation import deprecated from cubicweb.appobject import AppObject from cubicweb.view import NOINDEX, NOFOLLOW @@ -76,8 +70,7 @@ found """ -@add_metaclass(metafieldsform) -class Form(AppObject): +class Form(AppObject, metaclass=metafieldsform): __registry__ = 'forms' parent_form = None @@ -274,10 +267,7 @@ try: return self.form_valerror.errors.pop(field.role_name()) except KeyError: - if field.role and field.name in self.form_valerror: - warn('%s: errors key of attribute/relation should be suffixed by "-"' - % self.form_valerror.__class__, DeprecationWarning) - return self.form_valerror.errors.pop(field.name) + pass return None def remaining_errors(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/formfields.py --- a/cubicweb/web/formfields.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/formfields.py Fri May 24 16:29:14 2019 +0200 @@ -68,8 +68,6 @@ import pytz -from six import PY2, text_type, string_types - from logilab.mtconverter import xml_escape from logilab.common import nullobject from logilab.common.date import ustrftime @@ -236,15 +234,9 @@ l.append('@%#x' % id(self)) return u'%s>' % ' '.join(l) - def __unicode__(self): + def __str__(self): return self.as_string(False) - if PY2: - def __str__(self): - return self.as_string(False).encode('UTF8') - else: - __str__ = __unicode__ - def __repr__(self): return self.as_string(True) @@ -290,7 +282,7 @@ return u'' if value is True: return u'1' - return text_type(value) + return str(value) def get_widget(self, form): """return the widget instance associated to this field""" @@ -825,7 +817,7 @@ def _process_form_value(self, form): value = form._cw.form.get(self.input_name(form)) - if isinstance(value, text_type): + if isinstance(value, str): # file modified using a text widget return Binary(value.encode(self.encoding(form))) return super(EditableFileField, self)._process_form_value(form) @@ -852,7 +844,7 @@ self.widget.attrs.setdefault('size', self.default_text_input_size) def _ensure_correctly_typed(self, form, value): - if isinstance(value, string_types): + if isinstance(value, str): value = value.strip() if not value: return None @@ -934,7 +926,7 @@ return self.format_single_value(req, 1.234) def _ensure_correctly_typed(self, form, value): - if isinstance(value, string_types): + if isinstance(value, str): value = value.strip() if not value: return None @@ -955,8 +947,7 @@ def format_single_value(self, req, value): if value: - value = format_time(value.days * 24 * 3600 + value.seconds) - return text_type(value) + return format_time(value.days * 24 * 3600 + value.seconds) return u'' def example_format(self, req): @@ -966,7 +957,7 @@ return u'20s, 10min, 24h, 4d' def _ensure_correctly_typed(self, form, value): - if isinstance(value, string_types): + if isinstance(value, str): value = value.strip() if not value: return None @@ -996,14 +987,14 @@ return self.format_single_value(req, datetime.now()) def _ensure_correctly_typed(self, form, value): - if isinstance(value, string_types): + if isinstance(value, str): value = value.strip() if not value: return None try: value = form._cw.parse_datetime(value, self.etype) except ValueError as ex: - raise ProcessFormError(text_type(ex)) + raise ProcessFormError(str(ex)) return value @@ -1107,7 +1098,7 @@ linkedto = form.linked_to.get((self.name, self.role)) if linkedto: buildent = form._cw.entity_from_eid - return [(buildent(eid).view('combobox'), text_type(eid)) + return [(buildent(eid).view('combobox'), str(eid)) for eid in linkedto] return [] @@ -1119,7 +1110,7 @@ # vocabulary doesn't include current values, add them if form.edited_entity.has_eid(): rset = form.edited_entity.related(self.name, self.role) - vocab += [(e.view('combobox'), text_type(e.eid)) + vocab += [(e.view('combobox'), str(e.eid)) for e in rset.entities()] return vocab @@ -1153,11 +1144,11 @@ if entity.eid in done: continue done.add(entity.eid) - res.append((entity.view('combobox'), text_type(entity.eid))) + res.append((entity.view('combobox'), str(entity.eid))) return res def format_single_value(self, req, value): - return text_type(value) + return str(value) def process_form_value(self, form): """process posted form and return correctly typed value""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/formwidgets.py --- a/cubicweb/web/formwidgets.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/formwidgets.py Fri May 24 16:29:14 2019 +0200 @@ -98,8 +98,6 @@ from functools import reduce from datetime import date -from six import text_type, string_types - from logilab.mtconverter import xml_escape from logilab.common.date import todatetime @@ -272,7 +270,7 @@ """ posted = form._cw.form val = posted.get(field.input_name(form, self.suffix)) - if isinstance(val, string_types): + if isinstance(val, str): val = val.strip() return val @@ -463,7 +461,7 @@ options.append(u'') if 'size' not in attrs: if self._multiple: - size = text_type(min(self.default_size, len(vocab) or 1)) + size = str(min(self.default_size, len(vocab) or 1)) else: size = u'1' attrs['size'] = size @@ -727,7 +725,7 @@ else: value = self.value attrs = self.attributes(form, field) - attrs.setdefault('size', text_type(self.default_size)) + attrs.setdefault('size', str(self.default_size)) return tags.input(name=field.input_name(form, self.suffix), value=value, type='text', **attrs) @@ -801,7 +799,7 @@ try: date = todatetime(req.parse_datetime(datestr, 'Date')) except ValueError as exc: - raise ProcessFormError(text_type(exc)) + raise ProcessFormError(str(exc)) timestr = req.form.get(field.input_name(form, 'time')) if timestr: timestr = timestr.strip() @@ -810,7 +808,7 @@ try: time = req.parse_datetime(timestr, 'Time') except ValueError as exc: - raise ProcessFormError(text_type(exc)) + raise ProcessFormError(str(exc)) return date.replace(hour=time.hour, minute=time.minute, second=time.second) @@ -1014,12 +1012,12 @@ req = form._cw values = {} path = req.form.get(field.input_name(form, 'path')) - if isinstance(path, string_types): + if isinstance(path, str): path = path.strip() if path is None: path = u'' fqs = req.form.get(field.input_name(form, 'fqs')) - if isinstance(fqs, string_types): + if isinstance(fqs, str): fqs = fqs.strip() or None if fqs: for i, line in enumerate(fqs.split('\n')): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/htmlwidgets.py --- a/cubicweb/web/htmlwidgets.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/htmlwidgets.py Fri May 24 16:29:14 2019 +0200 @@ -24,9 +24,6 @@ import random from math import floor -from six import add_metaclass -from six.moves import range - from logilab.mtconverter import xml_escape from logilab.common.deprecation import class_deprecated @@ -118,8 +115,7 @@ self.w(u'
') -@add_metaclass(class_deprecated) -class SideBoxWidget(BoxWidget): +class SideBoxWidget(BoxWidget, metaclass=class_deprecated): """default CubicWeb's sidebox widget""" __deprecation_warning__ = '[3.10] class %(cls)s is deprecated' @@ -210,8 +206,7 @@ self.w(u'
') -@add_metaclass(class_deprecated) -class BoxField(HTMLWidget): +class BoxField(HTMLWidget, metaclass=class_deprecated): """couples label / value meant to be displayed in a box""" __deprecation_warning__ = '[3.10] class %(cls)s is deprecated' def __init__(self, label, value): @@ -224,8 +219,7 @@ % (self.label, self.value)) -@add_metaclass(class_deprecated) -class BoxSeparator(HTMLWidget): +class BoxSeparator(HTMLWidget, metaclass=class_deprecated): """a menu separator""" __deprecation_warning__ = '[3.10] class %(cls)s is deprecated' @@ -233,8 +227,7 @@ self.w(u'
    ') -@add_metaclass(class_deprecated) -class BoxLink(HTMLWidget): +class BoxLink(HTMLWidget, metaclass=class_deprecated): """a link in a box""" __deprecation_warning__ = '[3.10] class %(cls)s is deprecated' def __init__(self, href, label, _class='', title='', ident='', escape=False): @@ -256,8 +249,7 @@ self.w(u'
  • %s
  • \n' % (self._class, link)) -@add_metaclass(class_deprecated) -class BoxHtml(HTMLWidget): +class BoxHtml(HTMLWidget, metaclass=class_deprecated): """a form in a box""" __deprecation_warning__ = '[3.10] class %(cls)s is deprecated' def __init__(self, rawhtml): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/http_headers.py --- a/cubicweb/web/http_headers.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/http_headers.py Fri May 24 16:29:14 2019 +0200 @@ -6,9 +6,7 @@ from calendar import timegm import base64 import re - -from six import string_types -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse def dashCapitalize(s): @@ -383,7 +381,7 @@ def unique(seq): '''if seq is not a string, check it's a sequence of one element and return it''' - if isinstance(seq, string_types): + if isinstance(seq, str): return seq if len(seq) != 1: raise ValueError('single value required, not %s' % seq) @@ -455,10 +453,10 @@ """ if (value in (True, 1) or - isinstance(value, string_types) and value.lower() == 'true'): + isinstance(value, str) and value.lower() == 'true'): return 'true' if (value in (False, 0) or - isinstance(value, string_types) and value.lower() == 'false'): + isinstance(value, str) and value.lower() == 'false'): return 'false' raise ValueError("Invalid true/false header value: %s" % value) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/httpcache.py --- a/cubicweb/web/httpcache.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/httpcache.py Fri May 24 16:29:14 2019 +0200 @@ -121,7 +121,7 @@ """return the date/time where this view should be considered as modified. Take care of possible related objects modifications. - /!\ must return GMT time /!\ + /!\\ must return GMT time /!\\ """ # XXX check view module's file modification time in dev mod ? ctime = datetime.utcnow() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/propertysheet.py --- a/cubicweb/web/propertysheet.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/propertysheet.py Fri May 24 16:29:14 2019 +0200 @@ -47,7 +47,7 @@ self.reset() context['sheet'] = self context['lazystr'] = self.lazystr - self._percent_rgx = re.compile('%(?!\()') + self._percent_rgx = re.compile(r'%(?!\()') def lazystr(self, str): return lazystr(str, self) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/request.py --- a/cubicweb/web/request.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/request.py Fri May 24 16:29:14 2019 +0200 @@ -23,18 +23,14 @@ from hashlib import sha1 # pylint: disable=E0611 from calendar import timegm from datetime import date, datetime -from warnings import warn +import http.client from io import BytesIO - -from six import PY2, text_type, string_types -from six.moves import http_client -from six.moves.urllib.parse import urlsplit, quote as urlquote -from six.moves.http_cookies import SimpleCookie +from urllib.parse import urlsplit, quote as urlquote +from http.cookies import SimpleCookie from rql.utils import rqlvar_maker from logilab.common.decorators import cached -from logilab.common.deprecation import deprecated from cubicweb import AuthenticationError from cubicweb.req import RequestSessionBase @@ -162,16 +158,6 @@ self.html_headers.define_var('pageid', pid, override=False) self.pageid = pid - def _get_json_request(self): - warn('[3.15] self._cw.json_request is deprecated, use self._cw.ajax_request instead', - DeprecationWarning, stacklevel=2) - return self.ajax_request - def _set_json_request(self, value): - warn('[3.15] self._cw.json_request is deprecated, use self._cw.ajax_request instead', - DeprecationWarning, stacklevel=2) - self.ajax_request = value - json_request = property(_get_json_request, _set_json_request) - @property def authmode(self): """Authentification mode of the instance @@ -220,12 +206,8 @@ encoding = self.encoding for param, val in params.items(): if isinstance(val, (tuple, list)): - if PY2: - val = [unicode(x, encoding) for x in val] if len(val) == 1: val = val[0] - elif PY2 and isinstance(val, str): - val = unicode(val, encoding) if param in self.no_script_form_params and val: val = self.no_script_form_param(param, val) if param == '_cwmsgid': @@ -286,7 +268,7 @@ return None def set_message(self, msg): - assert isinstance(msg, text_type) + assert isinstance(msg, str) self.reset_message() self._msg = msg @@ -299,7 +281,7 @@ def set_redirect_message(self, msg): # TODO - this should probably be merged with append_to_redirect_message - assert isinstance(msg, text_type) + assert isinstance(msg, str) msgid = self.redirect_message_id() self.session.data[msgid] = msg return msgid @@ -386,7 +368,7 @@ eids = form['eid'] except KeyError: raise NothingToEdit(self._('no selected entities')) - if isinstance(eids, string_types): + if isinstance(eids, str): eids = (eids,) for peid in eids: if withtype: @@ -472,12 +454,6 @@ Give maxage = None to have a "session" cookie expiring when the client close its browser """ - if isinstance(name, SimpleCookie): - warn('[3.13] set_cookie now takes name and value as two first ' - 'argument, not anymore cookie object and name', - DeprecationWarning, stacklevel=2) - secure = name[value]['secure'] - name, value = value, name[value].value if maxage: # don't check is None, 0 may be specified assert expires is None, 'both max age and expires cant be specified' expires = maxage + time.time() @@ -494,12 +470,8 @@ expires=expires, secure=secure, httponly=httponly) self.headers_out.addHeader('Set-cookie', cookie) - def remove_cookie(self, name, bwcompat=None): + def remove_cookie(self, name): """remove a cookie by expiring it""" - if bwcompat is not None: - warn('[3.13] remove_cookie now take only a name as argument', - DeprecationWarning, stacklevel=2) - name = bwcompat self.set_cookie(name, '', maxage=0, expires=date(2000, 1, 1)) def set_content_type(self, content_type, filename=None, encoding=None, @@ -545,7 +517,7 @@ :param localfile: if True, the default data dir prefix is added to the JS filename """ - if isinstance(jsfiles, string_types): + if isinstance(jsfiles, str): jsfiles = (jsfiles,) for jsfile in jsfiles: if localfile: @@ -565,7 +537,7 @@ the css inclusion. cf: http://msdn.microsoft.com/en-us/library/ms537512(VS.85).aspx """ - if isinstance(cssfiles, string_types): + if isinstance(cssfiles, str): cssfiles = (cssfiles,) if ieonly: if self.ie_browser(): @@ -635,7 +607,7 @@ lang_prefix = '' if self.lang is not None and self.vreg.config.get('language-mode') == 'url-prefix': lang_prefix = '%s/' % self.lang - return lang_prefix + path + return lang_prefix + str(path) def url(self, includeparams=True): """return currently accessed url""" @@ -688,23 +660,14 @@ # overwrite headers_out to forge a brand new not-modified response self.headers_out = self._forge_cached_headers() if self.http_method() in ('HEAD', 'GET'): - self.status_out = http_client.NOT_MODIFIED + self.status_out = http.client.NOT_MODIFIED else: - self.status_out = http_client.PRECONDITION_FAILED + self.status_out = http.client.PRECONDITION_FAILED # XXX replace by True once validate_cache bw compat method is dropped return self.status_out # XXX replace by False once validate_cache bw compat method is dropped return None - @deprecated('[3.18] use .is_client_cache_valid() method instead') - def validate_cache(self): - """raise a `StatusResponse` exception if a cached page along the way - exists and is still usable. - """ - status_code = self.is_client_cache_valid() - if status_code is not None: - raise StatusResponse(status_code) - # abstract methods to override according to the web front-end ############# def http_method(self): @@ -812,26 +775,13 @@ values = _parse_accept_header(accepteds, value_parser, value_sort_key) return (raw_value for (raw_value, parsed_value, score) in values) - @deprecated('[3.17] demote_to_html is deprecated as we always serve html') - def demote_to_html(self): - """helper method to dynamically set request content type to text/html - - The global doctype and xmldec must also be changed otherwise the browser - will display '<[' at the beginning of the page - """ - pass - - # xml doctype ############################################################# - def set_doctype(self, doctype, reset_xmldecl=None): + def set_doctype(self, doctype): """helper method to dynamically change page doctype :param doctype: the new doctype, e.g. '' """ - if reset_xmldecl is not None: - warn('[3.17] reset_xmldecl is deprecated as we only serve html', - DeprecationWarning, stacklevel=2) self.main_stream.set_doctype(doctype) # page data management #################################################### @@ -878,16 +828,6 @@ useragent = self.useragent() return useragent and 'MSIE' in useragent - @deprecated('[3.17] xhtml_browser is deprecated (xhtml is no longer served)') - def xhtml_browser(self): - """return True if the browser is considered as xhtml compatible. - - If the instance is configured to always return text/html and not - application/xhtml+xml, this method will always return False, even though - this is semantically different - """ - return False - def html_content_type(self): return 'text/html' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/schemaviewer.py --- a/cubicweb/web/schemaviewer.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/schemaviewer.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six import string_types - from logilab.common.ureports import Section, Title, Table, Link, Span, Text from yams.schema2dot import CARD_MAP @@ -228,7 +226,7 @@ elif isinstance(val, (list, tuple)): val = sorted(val) val = ', '.join(str(v) for v in val) - elif val and isinstance(val, string_types): + elif val and isinstance(val, str): val = _(val) else: val = str(val) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_blog/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_blog/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_blog/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_blog/entities.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_blog/entities.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,7 @@ +from cubicweb.entities import AnyEntity, fetch_config + + +class BlogEntry(AnyEntity): + __regid__ = 'BlogEntry' + fetch_attrs, cw_fetch_order = fetch_config( + ['creation_date', 'title'], order='DESC') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_blog/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_blog/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,22 @@ +from yams.buildobjs import EntityType, String, RichString, SubjectRelation +from cubicweb.schema import WorkflowableEntityType, ERQLExpression + + +class Blog(EntityType): + title = String(maxsize=50, required=True) + description = RichString() + rss_url = String(maxsize=128, description=( + 'blog\'s rss url (useful for when using external site such as feedburner)')) + + +class BlogEntry(WorkflowableEntityType): + __permissions__ = { + 'read': ('managers', 'users', ERQLExpression('X in_state S, S name "published"'),), + 'add': ('managers', 'users'), + 'update': ('managers', 'owners'), + 'delete': ('managers', 'owners') + } + title = String(required=True, fulltextindexed=True, maxsize=256) + content = RichString(required=True, fulltextindexed=True) + entry_of = SubjectRelation('Blog') + same_as = SubjectRelation('ExternalUri') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/data/file.png diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/entities.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/entities.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,50 @@ +from logilab.mtconverter import guess_mimetype_and_encoding +from cubicweb.entities import AnyEntity, fetch_config + + +class File(AnyEntity): + """customized class for File entities""" + __regid__ = 'File' + fetch_attrs, cw_fetch_order = fetch_config(['data_name', 'title']) + + def set_format_and_encoding(self): + """try to set format and encoding according to known values (filename, + file content, format, encoding). + + This method must be called in a before_[add|update]_entity hook else it + won't have any effect. + """ + assert 'data' in self.cw_edited, "missing mandatory attribute data" + if self.cw_edited.get('data'): + if (hasattr(self.data, 'filename') + and not self.cw_edited.get('data_name')): + self.cw_edited['data_name'] = self.data.filename + else: + self.cw_edited['data_format'] = None + self.cw_edited['data_encoding'] = None + self.cw_edited['data_name'] = None + return + if 'data_format' in self.cw_edited: + format = self.cw_edited.get('data_format') + else: + format = None + if 'data_encoding' in self.cw_edited: + encoding = self.cw_edited.get('data_encoding') + else: + encoding = None + if not (format and encoding): + format, encoding = guess_mimetype_and_encoding( + data=self.cw_edited.get('data'), + # use get and not get_value since data has changed, we only + # want to consider explicitly specified values, not old ones + filename=self.cw_edited.get('data_name'), + format=format, encoding=encoding, + fallbackencoding=self._cw.encoding) + if format: + self.cw_edited['data_format'] = str(format) + if encoding: + self.cw_edited['data_encoding'] = str(encoding) + + +class UnResizeable(Exception): + pass diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/hooks.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/hooks.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,69 @@ +import os + +from cubicweb.server import hook +from cubicweb.predicates import is_instance +from cubicweb.entities import adapters + +from cubicweb_file.entities import UnResizeable + + +class UpdateFileHook(hook.Hook): + """a file has been updated, check data_format/data_encoding consistency + """ + __regid__ = 'updatefilehook' + __select__ = hook.Hook.__select__ & is_instance('File') + events = ('before_add_entity', 'before_update_entity',) + order = -1 # should be run before other hooks + category = 'hash' + + def __call__(self): + edited = self.entity.cw_edited + if 'data' in edited: + self.entity.set_format_and_encoding() + maxsize = None + if maxsize and self.entity.data_format.startswith('image/'): + iimage = self.entity.cw_adapt_to('IImage') + try: + edited['data'] = iimage.resize(maxsize) + except UnResizeable: + # if the resize fails for some reason, do nothing + # (original image will be stored) + pass + + # thumbnail cache invalidation + if 'update' in self.event and 'data' in edited: + thumb = self.entity.cw_adapt_to('IThumbnail') + if not thumb: + return + thumbpath = thumb.thumbnail_path() + if thumbpath: + try: + os.unlink(thumbpath) + except Exception as exc: + self.warning( + 'could not invalidate thumbnail file `%s` ' + '(cause: %s)', + thumbpath, exc) + + +class FileIDownloadableAdapter(adapters.IDownloadableAdapter): + __select__ = is_instance('File') + + # IDownloadable + def download_url(self, **kwargs): + # include filename in download url for nicer url + name = self._cw.url_quote(self.download_file_name()) + path = '%s/raw/%s' % (self.entity.rest_path(), name) + return self._cw.build_url(path, **kwargs) + + def download_content_type(self): + return self.entity.data_format + + def download_encoding(self): + return self.entity.data_encoding + + def download_file_name(self): + return self.entity.data_name + + def download_data(self): + return self.entity.data.getvalue() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,27 @@ +from yams.buildobjs import EntityType, String, Bytes, RichString + + +class File(EntityType): + """a downloadable file which may contains binary data""" + title = String(fulltextindexed=True, maxsize=256) + data = Bytes(required=True, description='file to upload') + data_format = String( + required=True, maxsize=128, + description=('MIME type of the file. Should be dynamically set ' + 'at upload time.')) + data_encoding = String( + maxsize=32, + description=('encoding of the file when it applies (e.g. text). ' + 'Should be dynamically set at upload time.')) + data_name = String( + required=True, fulltextindexed=True, + description=('name of the file. Should be dynamically set ' + 'at upload time.')) + data_hash = String( + maxsize=256, # max len of currently available hash alg + prefix is 140 + description=('hash of the file. May be set at upload time.'), + __permissions__={'read': ('managers', 'users', 'guests'), + 'add': (), + 'update': ()}) + description = RichString(fulltextindexed=True, internationalizable=True, + default_format='text/rest') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/uiprops.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/uiprops.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,1 @@ +FILE_ICON = data('file.png') # noqa: F821 diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/views.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/views.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,9 @@ +from cubicweb.web import formwidgets as wdgs +from cubicweb.web.views import uicfg + +# fields required in the schema but automatically set by hooks. Tell about that +# to the ui +_pvdc = uicfg.autoform_field_kwargs +_pvdc.tag_attribute(('File', 'data_name'), { + 'required': False, 'widget': wdgs.TextInput({'size': 45})}) +_pvdc.tag_attribute(('File', 'data_format'), {'required': False}) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_file/wdoc/toc.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_file/wdoc/toc.xml Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,6 @@ + +
    + Add an attachement + Ajouter un fichier +
    +
    diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_tag/__init__.py diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_tag/__pkginfo__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_tag/__pkginfo__.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,2 @@ +numversion = (1, 2, 3) +version = "1.2.3" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_tag/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_tag/schema.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,17 @@ +from yams.buildobjs import EntityType, String, SubjectRelation, RelationType + + +class Tag(EntityType): + """tags are used by users to mark entities. + When you include the Tag entity, all application specific entities + may then be tagged using the "tags" relation. + """ + name = String(required=True, fulltextindexed=True, unique=True, + maxsize=128) + # when using this component, add the Tag tag X relation for each type that + # should be taggeable + tags = SubjectRelation('Tag', description="tagged objects") + + +class tags(RelationType): + """indicates that an entity is classified by a given tag""" diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/data/cubicweb_tag/views.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/test/data/cubicweb_tag/views.py Fri May 24 16:29:14 2019 +0200 @@ -0,0 +1,28 @@ +from cubicweb.web import component +from cubicweb.web.views import ajaxcontroller + + +@ajaxcontroller.ajaxfunc +def tag_entity(self, eid, taglist): + execute = self._cw.execute + # get list of tag for this entity + tagged_by = set(tagname for (tagname,) in + execute('Any N WHERE T name N, T tags X, X eid %(x)s', + {'x': eid})) + for tagname in taglist: + tagname = tagname.strip() + if not tagname or tagname in tagged_by: + continue + tagrset = execute('Tag T WHERE T name %(name)s', {'name': tagname}) + if tagrset: + rql = 'SET T tags X WHERE T eid %(t)s, X eid %(x)s' + execute(rql, {'t': tagrset[0][0], 'x': eid}) + else: + rql = 'INSERT Tag T: T name %(name)s, T tags X WHERE X eid %(x)s' + execute(rql, {'name': tagname, 'x': eid}) + + +class TagsBox(component.AjaxEditRelationCtxComponent): + __regid__ = 'tags_box' + rtype = 'tags' + role = 'object' diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_application.py --- a/cubicweb/web/test/unittest_application.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_application.py Fri May 24 16:29:14 2019 +0200 @@ -18,10 +18,8 @@ """unit tests for cubicweb.web.application""" import base64 - -from six import text_type -from six.moves import http_client -from six.moves.http_cookies import SimpleCookie +import http.client +from http.cookies import SimpleCookie from logilab.common.testlib import TestCase, unittest_main from logilab.common.decorators import clear_cache @@ -167,7 +165,7 @@ def test_publish_validation_error(self): with self.admin_access.web_request() as req: user = req.user - eid = text_type(user.eid) + eid = str(user.eid) req.form = { 'eid': eid, '__type:' + eid: 'CWUser', @@ -248,8 +246,8 @@ self.config.global_set_option('language-mode', 'http-negotiation') orig_translations = self.config.translations.copy() self.config.translations = { - 'fr': (text_type, lambda x, y: text_type(y)), - 'en': (text_type, lambda x, y: text_type(y))} + 'fr': (str, lambda x, y: str(y)), + 'en': (str, lambda x, y: str(y))} try: headers = {'Accept-Language': 'fr'} with self.admin_access.web_request(headers=headers) as req: @@ -336,8 +334,8 @@ parent_eid = parent_eid or '__cubicweb_internal_field__' with self.admin_access.web_request() as req: req.form = { - 'eid': text_type(dir_eid), - '__maineid': text_type(dir_eid), + 'eid': str(dir_eid), + '__maineid': str(dir_eid), '__type:%s' % dir_eid: etype, 'parent-%s:%s' % (role, dir_eid): parent_eid, } @@ -353,8 +351,8 @@ version_eid = version_eid or '__cubicweb_internal_field__' with self.admin_access.web_request() as req: req.form = { - 'eid': text_type(ticket_eid), - '__maineid': text_type(ticket_eid), + 'eid': str(ticket_eid), + '__maineid': str(ticket_eid), '__type:%s' % ticket_eid: 'Ticket', 'in_version-subject:%s' % ticket_eid: version_eid, } @@ -395,8 +393,8 @@ with self.admin_access.web_request() as req: req.form = { - 'eid': (text_type(topd.eid), u'B'), - '__maineid': text_type(topd.eid), + 'eid': (str(topd.eid), u'B'), + '__maineid': str(topd.eid), '__type:%s' % topd.eid: 'Directory', '__type:B': 'Directory', 'parent-object:%s' % topd.eid: u'B', @@ -569,8 +567,8 @@ cnx.commit() with self.admin_access.web_request() as req: - dir_eid = text_type(mydir.eid) - perm_eid = text_type(perm.eid) + dir_eid = str(mydir.eid) + perm_eid = str(perm.eid) req.form = { 'eid': [dir_eid, perm_eid], '__maineid': dir_eid, @@ -596,7 +594,7 @@ with self.admin_access.web_request(vid='test.ajax.error', url='') as req: req.ajax_request = True app.handle_request(req) - self.assertEqual(http_client.INTERNAL_SERVER_ERROR, + self.assertEqual(http.client.INTERNAL_SERVER_ERROR, req.status_out) def _test_cleaned(self, kwargs, injected, cleaned): @@ -760,18 +758,6 @@ req.form['rql'] = 'rql:Any OV1, X WHERE X custom_workflow OV1?' self.app_handle_request(req) - def test_handle_deprecation(self): - """Test deprecation warning for *_handle methods.""" - with self.admin_access.web_request(url='foo') as req: - with self.assertWarns(DeprecationWarning) as cm: - self.app.core_handle(req, 'foo') - self.assertIn('path argument got removed from "core_handle"', - str(cm.warning)) - with self.assertWarns(DeprecationWarning) as cm: - self.app.main_handle_request('foo', req) - self.assertIn('entry point arguments are now (req, path)', - str(cm.warning)) - if __name__ == '__main__': unittest_main() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_form.py --- a/cubicweb/web/test/unittest_form.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_form.py Fri May 24 16:29:14 2019 +0200 @@ -19,8 +19,6 @@ import time from datetime import datetime -from six import text_type - import pytz from lxml import html @@ -80,19 +78,19 @@ t = req.create_entity('Tag', name=u'x') form1 = self.vreg['forms'].select('edition', req, entity=t) choices = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)] - self.assertIn(text_type(b.eid), choices) + self.assertIn(str(b.eid), choices) form2 = self.vreg['forms'].select('edition', req, entity=b) choices = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)] - self.assertIn(text_type(t.eid), choices) + self.assertIn(str(t.eid), choices) b.cw_clear_all_caches() t.cw_clear_all_caches() req.cnx.execute('SET X tags Y WHERE X is Tag, Y is BlogEntry') choices = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)] - self.assertIn(text_type(b.eid), choices) + self.assertIn(str(b.eid), choices) choices = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)] - self.assertIn(text_type(t.eid), choices) + self.assertIn(str(t.eid), choices) def test_form_field_choices_new_entity(self): with self.admin_access.web_request() as req: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_magicsearch.py --- a/cubicweb/web/test/unittest_magicsearch.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_magicsearch.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ import sys from contextlib import contextmanager -from six.moves import range - from logilab.common.testlib import TestCase, unittest_main from rql import BadRQLQuery, RQLSyntaxError diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_request.py --- a/cubicweb/web/test/unittest_request.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_request.py Fri May 24 16:29:14 2019 +0200 @@ -4,8 +4,6 @@ import unittest from functools import partial -from six import text_type - from cubicweb.devtools.fake import FakeConfig, FakeCWRegistryStore from cubicweb.web.request import (CubicWebRequestBase, _parse_accept_header, @@ -84,7 +82,7 @@ vreg = FakeCWRegistryStore(FakeConfig(), initlog=False) vreg.config['base-url'] = 'http://testing.fr/cubicweb/' vreg.config['language-mode'] = 'url-prefix' - vreg.config.translations['fr'] = text_type, text_type + vreg.config.translations['fr'] = str, str req = CubicWebRequestBase(vreg) # Override from_controller to avoid getting into relative_path method, # which is not implemented in CubicWebRequestBase. diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_urlrewrite.py --- a/cubicweb/web/test/unittest_urlrewrite.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_urlrewrite.py Fri May 24 16:29:14 2019 +0200 @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from six import text_type - from logilab.common import tempattr from cubicweb.devtools.testlib import CubicWebTC @@ -139,8 +137,8 @@ rgx_action(r'Any X WHERE X surname %(sn)s, ' 'X firstname %(fn)s', argsgroups=('sn', 'fn'), - transforms={'sn' : text_type.capitalize, - 'fn' : text_type.lower,})), + transforms={'sn' : str.capitalize, + 'fn' : str.lower,})), ] with self.admin_access.web_request() as req: rewriter = TestSchemaBasedRewriter(req) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_views_basecontrollers.py --- a/cubicweb/web/test/unittest_views_basecontrollers.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_views_basecontrollers.py Fri May 24 16:29:14 2019 +0200 @@ -18,14 +18,11 @@ """cubicweb.web.views.basecontrollers unit tests""" import time - -from six import text_type -from six.moves.urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs +from urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs import lxml from logilab.common.testlib import unittest_main -from logilab.common.decorators import monkeypatch from cubicweb import Binary, NoSelectableObject, ValidationError, transaction as tx from cubicweb.schema import RRQLExpression @@ -37,7 +34,6 @@ from cubicweb.uilib import rql_for_eid from cubicweb.web import Redirect, RemoteCallFailed, http_headers, formfields as ff from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes -from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction from cubicweb.server.session import Connection from cubicweb.server.hook import Hook, Operation @@ -94,7 +90,7 @@ } with self.assertRaises(ValidationError) as cm: self.ctrl_publish(req) - cm.exception.translate(text_type) + cm.exception.translate(str) expected = { '': u'some relations violate a unicity constraint', 'login': u'login is part of violated unicity constraint', @@ -151,12 +147,12 @@ user = req.user groupeids = [eid for eid, in req.execute('CWGroup G WHERE G name ' 'in ("managers", "users")')] - groups = [text_type(eid) for eid in groupeids] - eid = text_type(user.eid) + groups = [str(eid) for eid in groupeids] + eid = str(user.eid) req.form = { 'eid': eid, '__type:'+eid: 'CWUser', '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject', - 'login-subject:'+eid: text_type(user.login), + 'login-subject:'+eid: str(user.login), 'surname-subject:'+eid: u'Th\xe9nault', 'firstname-subject:'+eid: u'Sylvain', 'in_group-subject:'+eid: groups, @@ -174,7 +170,7 @@ self.create_user(cnx, u'user') cnx.commit() with self.new_access(u'user').web_request() as req: - eid = text_type(req.user.eid) + eid = str(req.user.eid) req.form = { 'eid': eid, '__maineid' : eid, '__type:'+eid: 'CWUser', @@ -194,12 +190,12 @@ with self.admin_access.web_request() as req: user = req.user groupeids = [g.eid for g in user.in_group] - eid = text_type(user.eid) + eid = str(user.eid) req.form = { 'eid': eid, '__type:'+eid: 'CWUser', '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject', - 'login-subject:'+eid: text_type(user.login), + 'login-subject:'+eid: str(user.login), 'firstname-subject:'+eid: u'Th\xe9nault', 'surname-subject:'+eid: u'Sylvain', } @@ -222,7 +218,7 @@ 'login-subject:X': u'adim', 'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto', 'surname-subject:X': u'Di Mascio', - 'in_group-subject:X': text_type(gueid), + 'in_group-subject:X': str(gueid), '__type:Y': 'EmailAddress', '_cw_entity_fields:Y': 'address-subject,use_email-object', @@ -289,7 +285,7 @@ # non regression test for #3120495. Without the fix, leads to # "unhashable type: 'list'" error with self.admin_access.web_request() as req: - cwrelation = text_type(req.execute('CWEType X WHERE X name "CWSource"')[0][0]) + cwrelation = str(req.execute('CWEType X WHERE X name "CWSource"')[0][0]) req.form = {'eid': [cwrelation], '__maineid' : cwrelation, '__type:'+cwrelation: 'CWEType', @@ -302,7 +298,7 @@ def test_edit_multiple_linked(self): with self.admin_access.web_request() as req: - peid = text_type(self.create_user(req, u'adim').eid) + peid = str(self.create_user(req, u'adim').eid) req.form = {'eid': [peid, 'Y'], '__maineid': peid, '__type:'+peid: u'CWUser', @@ -322,7 +318,7 @@ self.assertEqual(email.address, 'dima@logilab.fr') # with self.admin_access.web_request() as req: - emaileid = text_type(email.eid) + emaileid = str(email.eid) req.form = {'eid': [peid, emaileid], '__type:'+peid: u'CWUser', @@ -344,7 +340,7 @@ with self.admin_access.web_request() as req: user = req.user req.form = {'eid': 'X', - '__cloned_eid:X': text_type(user.eid), '__type:X': 'CWUser', + '__cloned_eid:X': str(user.eid), '__type:X': 'CWUser', '_cw_entity_fields:X': 'login-subject,upassword-subject', 'login-subject:X': u'toto', 'upassword-subject:X': u'toto', @@ -353,7 +349,7 @@ self.ctrl_publish(req) self.assertEqual({'upassword-subject': u'password and confirmation don\'t match'}, cm.exception.errors) - req.form = {'__cloned_eid:X': text_type(user.eid), + req.form = {'__cloned_eid:X': str(user.eid), 'eid': 'X', '__type:X': 'CWUser', '_cw_entity_fields:X': 'login-subject,upassword-subject', 'login-subject:X': u'toto', @@ -377,11 +373,11 @@ '__type:X': 'Salesterm', '_cw_entity_fields:X': 'amount-subject,described_by_test-subject', 'amount-subject:X': u'-10', - 'described_by_test-subject:X': text_type(feid), + 'described_by_test-subject:X': str(feid), } with self.assertRaises(ValidationError) as cm: self.ctrl_publish(req) - cm.exception.translate(text_type) + cm.exception.translate(str) self.assertEqual({'amount-subject': 'value -10 must be >= 0'}, cm.exception.errors) @@ -390,11 +386,11 @@ '__type:X': 'Salesterm', '_cw_entity_fields:X': 'amount-subject,described_by_test-subject', 'amount-subject:X': u'110', - 'described_by_test-subject:X': text_type(feid), + 'described_by_test-subject:X': str(feid), } with self.assertRaises(ValidationError) as cm: self.ctrl_publish(req) - cm.exception.translate(text_type) + cm.exception.translate(str) self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'}) with self.admin_access.web_request(rollbackfirst=True) as req: @@ -402,7 +398,7 @@ '__type:X': 'Salesterm', '_cw_entity_fields:X': 'amount-subject,described_by_test-subject', 'amount-subject:X': u'10', - 'described_by_test-subject:X': text_type(feid), + 'described_by_test-subject:X': str(feid), } self.expect_redirect_handle_request(req, 'edit') # should be redirected on the created @@ -421,7 +417,7 @@ # ensure a value that violate a constraint is properly detected with self.admin_access.web_request(rollbackfirst=True) as req: - req.form = {'eid': [text_type(seid)], + req.form = {'eid': [str(seid)], '__type:%s'%seid: 'Salesterm', '_cw_entity_fields:%s'%seid: 'amount-subject', 'amount-subject:%s'%seid: u'-10', @@ -432,7 +428,7 @@ # ensure a value that comply a constraint is properly processed with self.admin_access.web_request(rollbackfirst=True) as req: - req.form = {'eid': [text_type(seid)], + req.form = {'eid': [str(seid)], '__type:%s'%seid: 'Salesterm', '_cw_entity_fields:%s'%seid: 'amount-subject', 'amount-subject:%s'%seid: u'20', @@ -448,7 +444,7 @@ '__type:X': 'Salesterm', '_cw_entity_fields:X': 'amount-subject,described_by_test-subject', 'amount-subject:X': u'0', - 'described_by_test-subject:X': text_type(feid), + 'described_by_test-subject:X': str(feid), } # ensure a value that is modified in an operation on a modify @@ -556,7 +552,7 @@ def test_redirect_delete_button(self): with self.admin_access.web_request() as req: eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid - req.form = {'eid': text_type(eid), '__type:%s'%eid: 'BlogEntry', + req.form = {'eid': str(eid), '__type:%s'%eid: 'BlogEntry', '__action_delete': ''} path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'blogentry') @@ -565,14 +561,14 @@ req.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s', {'x': req.user.eid, 'e': eid}) req.cnx.commit() - req.form = {'eid': text_type(eid), '__type:%s'%eid: 'EmailAddress', + req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress', '__action_delete': ''} path, params = self.expect_redirect_handle_request(req, 'edit') self.assertEqual(path, 'cwuser/admin') self.assertIn('_cwmsgid', params) eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid - req.form = {'eid': [text_type(eid1), text_type(eid2)], + req.form = {'eid': [str(eid1), str(eid2)], '__type:%s'%eid1: 'BlogEntry', '__type:%s'%eid2: 'EmailAddress', '__action_delete': ''} @@ -674,13 +670,13 @@ groupeids = sorted(eid for eid, in req.execute('CWGroup G ' 'WHERE G name in ("managers", "users")')) - groups = [text_type(eid) for eid in groupeids] + groups = [str(eid) for eid in groupeids] cwetypeeid = req.execute('CWEType X WHERE X name "CWEType"')[0][0] - basegroups = [text_type(eid) + basegroups = [str(eid) for eid, in req.execute('CWGroup G ' 'WHERE X read_permission G, X eid %(x)s', {'x': cwetypeeid})] - cwetypeeid = text_type(cwetypeeid) + cwetypeeid = str(cwetypeeid) req.form = { 'eid': cwetypeeid, '__type:'+cwetypeeid: 'CWEType', @@ -760,8 +756,8 @@ with self.admin_access.web_request(url='edit') as req: p = self.create_user(req, u"doe") # do not try to skip 'primary_email' for this test - old_skips = p.__class__.skip_copy_for - p.__class__.skip_copy_for = () + old_skips = p.__class__.cw_skip_copy_for + p.__class__.cw_skip_copy_for = () try: e = req.create_entity('EmailAddress', address=u'doe@doe.com') req.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s', @@ -786,7 +782,7 @@ rset = req.execute('CWUser P WHERE P surname "Boom"') self.assertEqual(len(rset), 0) finally: - p.__class__.skip_copy_for = old_skips + p.__class__.cw_skip_copy_for = old_skips def test_regr_inlined_forms(self): with self.admin_access.web_request() as req: @@ -1020,64 +1016,6 @@ self.assertEqual(cm.exception.reason, 'no foo method') -class JSonControllerTC(AjaxControllerTC): - # NOTE: this class performs the same tests as AjaxController but with - # deprecated 'json' controller (i.e. check backward compatibility) - tested_controller = 'json' - - def setUp(self): - super(JSonControllerTC, self).setUp() - self.exposed_remote_funcs = [fname for fname in dir(JSonController) - if fname.startswith('js_')] - - def tearDown(self): - super(JSonControllerTC, self).tearDown() - for funcname in dir(JSonController): - # remove functions added dynamically during tests - if funcname.startswith('js_') and funcname not in self.exposed_remote_funcs: - delattr(JSonController, funcname) - - def test_monkeypatch_jsoncontroller(self): - with self.assertRaises(RemoteCallFailed): - with self.remote_calling('foo'): - pass - @monkeypatch(JSonController) - def js_foo(self): - return u'hello' - with self.remote_calling('foo') as (res, _): - self.assertEqual(res, b'hello') - - def test_monkeypatch_jsoncontroller_xhtmlize(self): - with self.assertRaises(RemoteCallFailed): - with self.remote_calling('foo'): - pass - @monkeypatch(JSonController) - @xhtmlize - def js_foo(self): - return u'hello' - with self.remote_calling('foo') as (res, _): - self.assertEqual(b'
    hello
    ', res) - - def test_monkeypatch_jsoncontroller_jsonize(self): - with self.assertRaises(RemoteCallFailed): - with self.remote_calling('foo'): - pass - @monkeypatch(JSonController) - @jsonize - def js_foo(self): - return 12 - with self.remote_calling('foo') as (res, _): - self.assertEqual(res, b'12') - - def test_monkeypatch_jsoncontroller_stdfunc(self): - @monkeypatch(JSonController) - @jsonize - def js_reledit_form(self): - return 12 - with self.remote_calling('reledit_form') as (res, _): - self.assertEqual(res, b'12') - - class UndoControllerTC(CubicWebTC): def setUp(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_views_errorform.py --- a/cubicweb/web/test/unittest_views_errorform.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_views_errorform.py Fri May 24 16:29:14 2019 +0200 @@ -51,7 +51,7 @@ req.data['ex'] = e html = self.view('error', req=req) self.assertTrue(re.search(b'^$', + b'value="[0-9a-f]{128}" />$', html.source, re.M)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_views_json.py --- a/cubicweb/web/test/unittest_views_json.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_views_json.py Fri May 24 16:29:14 2019 +0200 @@ -16,7 +16,6 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -from six import binary_type from cubicweb.devtools.testlib import CubicWebTC @@ -50,7 +49,7 @@ 'rql': u'Any GN,COUNT(X) GROUPBY GN ORDERBY GN ' 'WHERE X in_group G, G name GN'}) data = self.ctrl_publish(req, ctrl='jsonp') - self.assertIsInstance(data, binary_type) + self.assertIsInstance(data, bytes) self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/javascript']) # because jsonp anonymizes data, only 'guests' group should be found diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_viewselector.py --- a/cubicweb/web/test/unittest_viewselector.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_viewselector.py Fri May 24 16:29:14 2019 +0200 @@ -17,15 +17,14 @@ # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . """XXX rename, split, reorganize this""" -from __future__ import print_function +from logilab.common.registry import NoSelectableObject from logilab.common.testlib import unittest_main from cubicweb.devtools.testlib import CubicWebTC from cubicweb import Binary, UnknownProperty from cubicweb.predicates import (is_instance, specified_etype_implements, rql_condition) -from cubicweb.web import NoSelectableObject from cubicweb.web.action import Action from cubicweb.web.views import (primary, baseviews, tableview, diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/test/unittest_web.py --- a/cubicweb/web/test/unittest_web.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/test/unittest_web.py Fri May 24 16:29:14 2019 +0200 @@ -28,7 +28,7 @@ requests = None from logilab.common.testlib import TestCase, unittest_main -from cubicweb.devtools.httptest import CubicWebWsgiTC +from cubicweb.devtools.httptest import CubicWebServerTC from cubicweb.devtools.fake import FakeRequest class AjaxReplaceUrlTC(TestCase): @@ -56,7 +56,7 @@ req.html_headers.post_inlined_scripts[0]) -class FileUploadTC(CubicWebWsgiTC): +class FileUploadTC(CubicWebServerTC): def setUp(self): "Skip whole test class if a suitable requests module is not available" @@ -101,7 +101,7 @@ self.assertDictEqual(expect, loads(webreq.text)) -class LanguageTC(CubicWebWsgiTC): +class LanguageTC(CubicWebServerTC): def test_language_neg(self): headers = {'Accept-Language': 'fr'} @@ -132,7 +132,7 @@ self.assertIn('HttpOnly', webreq.getheader('set-cookie')) -class MiscOptionsTC(CubicWebWsgiTC): +class MiscOptionsTC(CubicWebServerTC): @classmethod def setUpClass(cls): super(MiscOptionsTC, cls).setUpClass() diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/uicfg.py --- a/cubicweb/web/uicfg.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -""" -This module has been moved to web.views.uicfg. -""" - - -from warnings import warn -from cubicweb.web.views.uicfg import * - - -warn('[3.16] moved to cubicweb.web.views.uicfg', - DeprecationWarning, stacklevel=2) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/uihelper.py --- a/cubicweb/web/uihelper.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/uihelper.py Fri May 24 16:29:14 2019 +0200 @@ -45,8 +45,6 @@ """ -from six import add_metaclass - from logilab.common.deprecation import deprecated from cubicweb.web.views import uicfg @@ -94,8 +92,7 @@ super(meta_formconfig, cls).__init__(name, bases, classdict) -@add_metaclass(meta_formconfig) -class FormConfig: +class FormConfig(metaclass=meta_formconfig): """helper base class to define uicfg rules on a given entity type. In all descriptions below, attributes list can either be a list of diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/__init__.py --- a/cubicweb/web/views/__init__.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/__init__.py Fri May 24 16:29:14 2019 +0200 @@ -19,15 +19,10 @@ -import os import sys -import tempfile - -from six import add_metaclass from rql import nodes from logilab.mtconverter import xml_escape -from logilab.common.deprecation import class_deprecated def need_table_view(rset, schema): @@ -126,23 +121,3 @@ return u'%s' % ( xml_escape(url), csscls, req.__('New %s' % etype)) return u'' - - - -@add_metaclass(class_deprecated) -class TmpFileViewMixin(object): - __deprecation_warning__ = '[3.18] %(cls)s is deprecated' - binary = True - content_type = 'application/octet-stream' - cache_max_age = 60*60*2 # stay in http cache for 2 hours by default - - def call(self): - self.cell_call() - - def cell_call(self, row=0, col=0): - self.cw_row, self.cw_col = row, col # in case one needs it - fd, tmpfile = tempfile.mkstemp('.png') - os.close(fd) - self._generate(tmpfile) - self.w(open(tmpfile, 'rb').read()) - os.unlink(tmpfile) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/ajaxcontroller.py --- a/cubicweb/web/views/ajaxcontroller.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/ajaxcontroller.py Fri May 24 16:29:14 2019 +0200 @@ -61,17 +61,11 @@ """ - - -from warnings import warn +import http.client as http_client from functools import partial -from six import PY2, text_type -from six.moves import http_client - from logilab.common.date import strptime from logilab.common.registry import yes -from logilab.common.deprecation import deprecated from cubicweb import ObjectNotFound, NoSelectableObject, ValidationError from cubicweb.appobject import AppObject @@ -119,23 +113,11 @@ except KeyError: raise RemoteCallFailed('no method specified', status=http_client.BAD_REQUEST) - # 1/ check first for old-style (JSonController) ajax func for bw compat try: - func = getattr(basecontrollers.JSonController, 'js_%s' % fname) - if PY2: - func = func.__func__ - func = partial(func, self) - except AttributeError: - # 2/ check for new-style (AjaxController) ajax func - try: - func = self._cw.vreg['ajax-func'].select(fname, self._cw) - except ObjectNotFound: - raise RemoteCallFailed('no %s method' % fname, - status=http_client.BAD_REQUEST) - else: - warn('[3.15] remote function %s found on JSonController, ' - 'use AjaxFunction / @ajaxfunc instead' % fname, - DeprecationWarning, stacklevel=2) + func = self._cw.vreg['ajax-func'].select(fname, self._cw) + except ObjectNotFound: + raise RemoteCallFailed('no %s method' % fname, + status=http_client.BAD_REQUEST) debug_mode = self._cw.vreg.config.debugmode # no attribute means the callback takes no argument args = self._cw.form.get('arg', ()) @@ -165,7 +147,7 @@ if result is None: return b'' # get unicode on @htmlize methods, encoded string on @jsonize methods - elif isinstance(result, text_type): + elif isinstance(result, str): return result.encode(self._cw.encoding) return result @@ -444,16 +426,6 @@ """remove user's session data associated to current pageid""" self._cw.session.data.pop(self._cw.pageid, None) -@ajaxfunc(output_type='json') -@deprecated("[3.13] use jQuery.cookie(cookiename, cookievalue, {path: '/'}) in js land instead") -def set_cookie(self, cookiename, cookievalue): - """generates the Set-Cookie HTTP reponse header corresponding - to `cookiename` / `cookievalue`. - """ - cookiename, cookievalue = str(cookiename), str(cookievalue) - self._cw.set_cookie(cookiename, cookievalue) - - @ajaxfunc def delete_relation(self, rtype, subjeid, objeid): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/authentication.py --- a/cubicweb/web/views/authentication.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/authentication.py Fri May 24 16:29:14 2019 +0200 @@ -17,8 +17,6 @@ # with CubicWeb. If not, see . """user authentication component""" -from six import text_type - from logilab.common.deprecation import class_renamed from logilab.common.textutils import unormalize @@ -114,8 +112,8 @@ self.sessionid = make_uid(unormalize(user.login)) self.data = {} - def __unicode__(self): - return '' % (text_type(self.user.login), id(self)) + def __str__(self): + return '' % (self.user.login, id(self)) @property def anonymous_session(self): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/autoform.py --- a/cubicweb/web/views/autoform.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/autoform.py Fri May 24 16:29:14 2019 +0200 @@ -118,8 +118,6 @@ .. Controlling the generic relation fields """ -from six.moves import range - from logilab.mtconverter import xml_escape from logilab.common.decorators import iclassmethod, cached from logilab.common.registry import NoSelectableObject diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/basecomponents.py --- a/cubicweb/web/views/basecomponents.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/basecomponents.py Fri May 24 16:29:14 2019 +0200 @@ -26,12 +26,9 @@ from logilab.mtconverter import xml_escape from logilab.common.registry import yes from logilab.common.deprecation import class_renamed -from rql import parse -from cubicweb.predicates import (match_form_params, match_context, - multi_etypes_rset, configuration_values, +from cubicweb.predicates import (match_context, configuration_values, anonymous_user, authenticated_user) -from cubicweb.schema import display_name from cubicweb.utils import wrap_on_write from cubicweb.uilib import toggle_action from cubicweb.web import component diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/basecontrollers.py --- a/cubicweb/web/views/basecontrollers.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/basecontrollers.py Fri May 24 16:29:14 2019 +0200 @@ -19,15 +19,7 @@ object to handle publication. """ - -from cubicweb import _ - -from warnings import warn - -from six import text_type -from six.moves import http_client - -from logilab.common.deprecation import deprecated +import http.client from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError, AuthenticationError, UndoTransactionException, @@ -35,44 +27,9 @@ from cubicweb.utils import json_dumps from cubicweb.predicates import (authenticated_user, anonymous_user, match_form_params) -from cubicweb.web import Redirect, RemoteCallFailed -from cubicweb.web.controller import Controller, append_url_params +from cubicweb.web import Redirect +from cubicweb.web.controller import Controller from cubicweb.web.views import vid_from_rset -import cubicweb.transaction as tx - -@deprecated('[3.15] jsonize is deprecated, use AjaxFunction appobjects instead') -def jsonize(func): - """decorator to sets correct content_type and calls `json_dumps` on - results - """ - def wrapper(self, *args, **kwargs): - self._cw.set_content_type('application/json') - return json_dumps(func(self, *args, **kwargs)) - wrapper.__name__ = func.__name__ - return wrapper - -@deprecated('[3.15] xhtmlize is deprecated, use AjaxFunction appobjects instead') -def xhtmlize(func): - """decorator to sets correct content_type and calls `xmlize` on results""" - def wrapper(self, *args, **kwargs): - self._cw.set_content_type(self._cw.html_content_type()) - result = func(self, *args, **kwargs) - return ''.join((u'
    ', result.strip(), - u'
    ')) - wrapper.__name__ = func.__name__ - return wrapper - -@deprecated('[3.15] check_pageid is deprecated, use AjaxFunction appobjects instead') -def check_pageid(func): - """decorator which checks the given pageid is found in the - user's session data - """ - def wrapper(self, *args, **kwargs): - data = self._cw.session.data.get(self._cw.pageid) - if data is None: - raise RemoteCallFailed(self._cw._('pageid-not-found')) - return func(self, *args, **kwargs) - return wrapper class LoginController(Controller): @@ -86,7 +43,7 @@ raise AuthenticationError() else: # Cookie authentication - self._cw.status_out = http_client.FORBIDDEN + self._cw.status_out = http.client.FORBIDDEN return self.appli.need_login_content(self._cw) class LoginControllerForAuthed(Controller): @@ -229,7 +186,7 @@ except Exception as ex: req.cnx.rollback() req.exception('unexpected error while validating form') - return (False, text_type(ex), ctrl._edited_entity) + return (False, str(ex), ctrl._edited_entity) return (False, '???', None) @@ -255,16 +212,6 @@ return self.response(domid, status, args, entity).encode(self._cw.encoding) -class JSonController(Controller): - __regid__ = 'json' - - def publish(self, rset=None): - warn('[3.15] JSONController is deprecated, use AjaxController instead', - DeprecationWarning) - ajax_controller = self._cw.vreg['controllers'].select('ajax', self._cw, appli=self.appli) - return ajax_controller.publish(rset) - - class MailBugReportController(Controller): __regid__ = 'reportbug' __select__ = match_form_params('description') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/baseviews.py --- a/cubicweb/web/views/baseviews.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/baseviews.py Fri May 24 16:29:14 2019 +0200 @@ -77,10 +77,6 @@ from cubicweb import _ -from warnings import warn - -from six.moves import range - from logilab.mtconverter import TransformError, xml_escape from logilab.common.registry import yes @@ -368,11 +364,8 @@ :param listid: the DOM id to use for the root element """ - if subvid is None and 'vid' in kwargs: - warn("should give a 'subvid' argument instead of 'vid'", - DeprecationWarning, stacklevel=2) - else: - kwargs['vid'] = subvid + assert 'vid' not in kwargs + kwargs['vid'] = subvid return super(SimpleListView, self).call(**kwargs) @@ -626,18 +619,3 @@ url = self.index_url(basepath, key[1], vtitle=vtitle) title = self._cw._('archive for %(author)s') % {'author': key[0]} return tags.a(label, href=url, title=title) - - -# bw compat #################################################################### - -from logilab.common.deprecation import class_moved, class_deprecated - -from cubicweb.web.views import boxes, xmlrss, primary, tableview -PrimaryView = class_moved(primary.PrimaryView) -SideBoxView = class_moved(boxes.SideBoxView) -XmlView = class_moved(xmlrss.XMLView) -XmlItemView = class_moved(xmlrss.XMLItemView) -XmlRsetView = class_moved(xmlrss.XMLRsetView) -RssView = class_moved(xmlrss.RSSView) -RssItemView = class_moved(xmlrss.RSSItemView) -TableView = class_moved(tableview.TableView) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/boxes.py --- a/cubicweb/web/views/boxes.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/boxes.py Fri May 24 16:29:14 2019 +0200 @@ -28,12 +28,7 @@ from cubicweb import _ -from warnings import warn - -from six import text_type, add_metaclass - from logilab.mtconverter import xml_escape -from logilab.common.deprecation import class_deprecated from cubicweb import Unauthorized from cubicweb.predicates import (match_user_groups, match_kwargs, @@ -215,7 +210,7 @@ @property def domid(self): - return super(RsetBox, self).domid + text_type(abs(id(self))) + text_type(abs(id(self.cw_rset))) + return super(RsetBox, self).domid + str(abs(id(self))) + str(abs(id(self.cw_rset))) def render_title(self, w): w(self.cw_extra_kwargs['title']) @@ -230,28 +225,6 @@ # helper classes ############################################################## -@add_metaclass(class_deprecated) -class SideBoxView(EntityView): - """helper view class to display some entities in a sidebox""" - __deprecation_warning__ = '[3.10] SideBoxView is deprecated, use RsetBox instead (%(cls)s)' - - __regid__ = 'sidebox' - - def call(self, title=u'', **kwargs): - """display a list of entities by calling their view""" - if 'dispctrl' in self.cw_extra_kwargs: - # XXX do not modify dispctrl! - self.cw_extra_kwargs['dispctrl'].setdefault('subvid', 'outofcontext') - self.cw_extra_kwargs['dispctrl'].setdefault('use_list_limit', 1) - if title: - self.cw_extra_kwargs['title'] = title - self.cw_extra_kwargs.setdefault('context', 'incontext') - box = self._cw.vreg['ctxcomponents'].select( - 'rsetbox', self._cw, rset=self.cw_rset, vid='autolimited', - **self.cw_extra_kwargs) - box.render(self.w) - - class ContextualBoxLayout(component.Layout): __select__ = match_context('incontext', 'left', 'right') & contextual() # predefined class in cubicweb.css: contextualBox | contextFreeBox diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/csvexport.py --- a/cubicweb/web/views/csvexport.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/csvexport.py Fri May 24 16:29:14 2019 +0200 @@ -20,9 +20,6 @@ from cubicweb import _ -from six import PY2 -from six.moves import range - from cubicweb.schema import display_name from cubicweb.predicates import any_rset, empty_rset from cubicweb.uilib import UnicodeCSVWriter @@ -32,7 +29,7 @@ """mixin class for CSV views""" templatable = False content_type = "text/comma-separated-values" - binary = PY2 # python csv module is unicode aware in py3k + binary = False csv_params = {'dialect': 'excel', 'quotechar': '"', 'delimiter': ';', diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/cwsources.py --- a/cubicweb/web/views/cwsources.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/cwsources.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ import logging -from six.moves import range - from logilab.common.decorators import cachedproperty from cubicweb import _ diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/cwuser.py --- a/cubicweb/web/views/cwuser.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/cwuser.py Fri May 24 16:29:14 2019 +0200 @@ -22,9 +22,6 @@ from hashlib import sha1 # pylint: disable=E0611 -from six import text_type -from six.moves import range - from logilab.mtconverter import xml_escape from cubicweb import tags @@ -252,6 +249,6 @@ 'group': tableview.MainEntityColRenderer(), 'nb_users': tableview.EntityTableColRenderer( header=_('num. users'), - renderfunc=lambda w, x: w(text_type(x.num_users())), + renderfunc=lambda w, x: w(str(x.num_users())), sortfunc=lambda x: x.num_users()), } diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/debug.py --- a/cubicweb/web/views/debug.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/debug.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ from time import strftime, localtime -from six import text_type - from logilab.mtconverter import xml_escape from cubicweb.predicates import none_rset, match_user_groups @@ -98,7 +96,7 @@ if k.endswith('_cache_size'): stats[k] = '%s / %s' % (stats[k]['size'], stats[k]['maxsize']) def format_stat(sname, sval): - return '%s %s' % (xml_escape(text_type(sval)), + return '%s %s' % (xml_escape(str(sval)), sname.endswith('percent') and '%' or '') pyvalue = [(sname, format_stat(sname, sval)) for sname, sval in sorted(stats.items())] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/editcontroller.py --- a/cubicweb/web/views/editcontroller.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/editcontroller.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ from datetime import datetime -from six import text_type - from logilab.common.graph import ordered_nodes from rql.utils import rqlvar_maker @@ -201,7 +199,7 @@ if '__linkto' in req.form and 'eid' in req.form: self.execute_linkto() elif '__delete' not in req.form: - raise ValidationError(None, {None: text_type(ex)}) + raise ValidationError(None, {None: str(ex)}) # all pending inlined relations to newly created entities have been # treated now (pop to ensure there are no attempt to add new ones) pending_inlined = req.data.pop('pending_inlined') @@ -234,7 +232,7 @@ autoform.delete_relations(self._cw, todelete) self._cw.remove_pending_operations() if self.errors: - errors = dict((f.name, text_type(ex)) for f, ex in self.errors) + errors = dict((f.name, str(ex)) for f, ex in self.errors) raise ValidationError(valerror_eid(form.get('__maineid')), errors) def _insert_entity(self, etype, eid, rqlquery): @@ -285,7 +283,7 @@ rqlquery.set_inlined(field.name, form_.edited_entity.eid) if not rqlquery.canceled: if self.errors: - errors = dict((f.role_name(), text_type(ex)) for f, ex in self.errors) + errors = dict((f.role_name(), str(ex)) for f, ex in self.errors) raise ValidationError(valerror_eid(entity.eid), errors) if eid is None: # creation or copy entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/editforms.py --- a/cubicweb/web/views/editforms.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/editforms.py Fri May 24 16:29:14 2019 +0200 @@ -23,10 +23,7 @@ from copy import copy -from six.moves import range - from logilab.common.registry import yes -from logilab.common.deprecation import class_moved from cubicweb import _ from cubicweb import tags @@ -299,9 +296,3 @@ copy_nav_params=True, formvid='edition') form.render(w=self.w) - - -# click and edit handling ('reledit') ########################################## - -ClickAndEditFormView = class_moved(reledit.ClickAndEditFormView) -AutoClickAndEditFormView = class_moved(reledit.AutoClickAndEditFormView) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/embedding.py --- a/cubicweb/web/views/embedding.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Objects interacting together to provides the external page embeding -functionality. -""" - -from logilab.common.deprecation import class_moved, moved - -try: - from cubes.embed.views import * - - IEmbedableAdapter = class_moved(IEmbedableAdapter, message='[3.17] IEmbedableAdapter moved to cubes.embed.views') - ExternalTemplate = class_moved(ExternalTemplate, message='[3.17] IEmbedableAdapter moved to cubes.embed.views') - EmbedController = class_moved(EmbedController, message='[3.17] IEmbedableAdapter moved to cubes.embed.views') - entity_has_embedable_url = moved('cubes.embed.views', 'entity_has_embedable_url') - EmbedAction = class_moved(EmbedAction, message='[3.17] EmbedAction moved to cubes.embed.views') - replace_href = class_moved(replace_href, message='[3.17] replace_href moved to cubes.embed.views') - embed_external_page = moved('cubes.embed.views', 'embed_external_page') - absolutize_links = class_moved(absolutize_links, message='[3.17] absolutize_links moved to cubes.embed.views') - prefix_links = moved('cubes.embed.views', 'prefix_links') -except ImportError: - from cubicweb.web import LOGGER - LOGGER.warning('[3.17] embedding extracted to cube embed that was not found. try installing it.') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/facets.py --- a/cubicweb/web/views/facets.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/facets.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from warnings import warn - from logilab.mtconverter import xml_escape from logilab.common.decorators import cachedproperty from logilab.common.registry import objectify_predicate, yes @@ -129,7 +127,7 @@ needs_js = ['cubicweb.ajax.js', 'cubicweb.facets.js'] needs_css = ['cubicweb.facets.css'] - def generate_form(self, w, rset, divid, vid, vidargs=None, mainvar=None, + def generate_form(self, w, rset, divid, vid, mainvar=None, paginate=False, cssclass='', hiddens=None, **kwargs): """display a form to filter some view's content @@ -163,12 +161,7 @@ self._cw.add_css(self.needs_css) self._cw.html_headers.define_var('facetLoadingMsg', self._cw._('facet-loading-msg')) - if vidargs is not None: - warn("[3.14] vidargs is deprecated. Maybe you're using some TableView?", - DeprecationWarning, stacklevel=2) - else: - vidargs = {} - vidargs = dict((k, v) for k, v in vidargs.items() if v) + vidargs = {} facetargs = xml_escape(json_dumps([divid, vid, paginate, vidargs])) w(u'' % (divid, cssclass, facetargs)) diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/formrenderers.py --- a/cubicweb/web/views/formrenderers.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/formrenderers.py Fri May 24 16:29:14 2019 +0200 @@ -37,8 +37,6 @@ from warnings import warn -from six import text_type - from logilab.mtconverter import xml_escape from logilab.common.registry import yes @@ -121,7 +119,7 @@ data.insert(0, errormsg) # NOTE: we call unicode because `tag` objects may be found within data # e.g. from the cwtags library - w(''.join(text_type(x) for x in data)) + w(''.join(str(x) for x in data)) def render_content(self, w, form, values): if self.display_progress_div: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/forms.py --- a/cubicweb/web/views/forms.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/forms.py Fri May 24 16:29:14 2019 +0200 @@ -45,8 +45,6 @@ import time import inspect -from six import text_type - from logilab.common import dictattr, tempattr from logilab.common.decorators import iclassmethod, cached from logilab.common.textutils import splitstrip @@ -286,7 +284,7 @@ except ProcessFormError as exc: errors.append((field, exc)) if errors: - errors = dict((f.role_name(), text_type(ex)) for f, ex in errors) + errors = dict((f.role_name(), str(ex)) for f, ex in errors) raise ValidationError(None, errors) return processed diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/ibreadcrumbs.py --- a/cubicweb/web/views/ibreadcrumbs.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/ibreadcrumbs.py Fri May 24 16:29:14 2019 +0200 @@ -22,8 +22,6 @@ from warnings import warn -from six import text_type - from logilab.mtconverter import xml_escape from cubicweb import tags, uilib @@ -146,7 +144,7 @@ xml_escape(url), xml_escape(uilib.cut(title, textsize)))) else: textsize = self._cw.property_value('navigation.short-line-size') - w(xml_escape(uilib.cut(text_type(part), textsize))) + w(xml_escape(uilib.cut(str(part), textsize))) class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/idownloadable.py --- a/cubicweb/web/views/idownloadable.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/idownloadable.py Fri May 24 16:29:14 2019 +0200 @@ -22,10 +22,7 @@ from cubicweb import _ -from six.moves import range - from logilab.mtconverter import BINARY_ENCODINGS, TransformError, xml_escape -from logilab.common.deprecation import class_renamed, deprecated from cubicweb import tags from cubicweb.view import EntityView diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/igeocodable.py --- a/cubicweb/web/views/igeocodable.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Specific views for entities implementing IGeocodable""" - -try: - from cubes.geocoding.views import (IGeocodableAdapter, - GeocodingJsonView, - GoogleMapBubbleView, - GoogleMapsView, - GoogeMapsLegend) - - from logilab.common.deprecation import class_moved - - msg = '[3.17] cubicweb.web.views.igeocodable moved to cubes.geocoding.views' - IGeocodableAdapter = class_moved(IGeocodableAdapter, message=msg) - GeocodingJsonView = class_moved(GeocodingJsonView, message=msg) - GoogleMapBubbleView = class_moved(GoogleMapBubbleView, message=msg) - GoogleMapsView = class_moved(GoogleMapsView, message=msg) - GoogeMapsLegend = class_moved(GoogeMapsLegend, message=msg) -except ImportError: - from cubicweb.web import LOGGER - LOGGER.warning('[3.17] igeocoding extracted to cube geocoding that was not found. try installing it.') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/isioc.py --- a/cubicweb/web/views/isioc.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Specific views for SIOC (Semantically-Interlinked Online Communities) - -http://sioc-project.org -""" - -from logilab.common.deprecation import class_moved - -try: - from cubes.sioc.views import * - - ISIOCItemAdapter = class_moved(ISIOCItemAdapter, message='[3.17] ISIOCItemAdapter moved to cubes.isioc.views') - ISIOCContainerAdapter = class_moved(ISIOCContainerAdapter, message='[3.17] ISIOCContainerAdapter moved to cubes.isioc.views') - SIOCView = class_moved(SIOCView, message='[3.17] SIOCView moved to cubes.is.view') - SIOCContainerView = class_moved(SIOCContainerView, message='[3.17] SIOCContainerView moved to cubes.is.view') - SIOCItemView = class_moved(SIOCItemView, message='[3.17] SIOCItemView moved to cubes.is.view') -except ImportError: - from cubicweb.web import LOGGER - LOGGER.warning('[3.17] isioc extracted to cube sioc that was not found. try installing it.') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/magicsearch.py --- a/cubicweb/web/views/magicsearch.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/magicsearch.py Fri May 24 16:29:14 2019 +0200 @@ -23,8 +23,6 @@ import re from logging import getLogger -from six import text_type - from yams.interfaces import IVocabularyConstraint from rql import RQLSyntaxError, BadRQLQuery, parse @@ -98,7 +96,7 @@ def resolve_ambiguities(var_types, ambiguous_nodes, schema): """Tries to resolve remaining ambiguities for translation - /!\ An ambiguity is when two different string can be localized with + /!\\ An ambiguity is when two different string can be localized with the same string A simple example: - 'name' in a company context will be localized as 'nom' in French @@ -388,7 +386,7 @@ self.processors = sorted(processors, key=lambda x: x.priority) def process_query(self, uquery): - assert isinstance(uquery, text_type) + assert isinstance(uquery, str) try: procname, query = uquery.split(':', 1) proc = self.by_name[procname.strip().lower()] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/massmailing.py --- a/cubicweb/web/views/massmailing.py Tue May 21 10:50:08 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""Mass mailing handling: send mail to entities adaptable to IEmailable""" - -try: - from cubes.massmailing.views import (SendEmailAction, - recipient_vocabulary, - MassMailingForm, - MassMailingFormRenderer, - MassMailingFormView, - SendMailController) - - - from logilab.common.deprecation import class_moved, moved - - msg = '[3.17] cubicweb.web.views.massmailing moved to cubes.massmailing.views' - SendEmailAction = class_moved(SendEmailAction, message=msg) - recipient_vocabulary = moved('cubes.massmailing.views', 'recipient_vocabulary') - MassMailingForm = class_moved(MassMailingForm, message=msg) - MassMailingFormRenderer = class_moved(MassMailingFormRenderer, message=msg) - MassMailingFormView = class_moved(MassMailingFormView, message=msg) - SendMailController = class_moved(SendMailController, message=msg) -except ImportError: - from cubicweb.web import LOGGER - LOGGER.warning('[3.17] massmailing extracted to cube massmailing that was not found. try installing it.') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/navigation.py --- a/cubicweb/web/views/navigation.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/navigation.py Fri May 24 16:29:14 2019 +0200 @@ -50,12 +50,9 @@ from datetime import datetime -from six import text_type - from rql.nodes import VariableRef, Constant from logilab.mtconverter import xml_escape -from logilab.common.deprecation import deprecated from cubicweb.predicates import paginated_rset, sorted_rset, adaptable from cubicweb.uilib import cut @@ -194,10 +191,10 @@ return entity.printable_value(attrname, format='text/plain') elif col is None: # smart links disabled. def index_display(row): - return text_type(row) + return str(row) elif self._cw.vreg.schema.eschema(rset.description[0][col]).final: def index_display(row): - return text_type(rset[row][col]) + return str(rset[row][col]) else: def index_display(row): return rset.get_entity(row, col).view('text') diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/owl.py --- a/cubicweb/web/views/owl.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/owl.py Fri May 24 16:29:14 2019 +0200 @@ -21,8 +21,6 @@ from cubicweb import _ -from six.moves import range - from logilab.mtconverter import TransformError, xml_escape from cubicweb.view import StartupView, EntityView diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/plots.py --- a/cubicweb/web/views/plots.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/plots.py Fri May 24 16:29:14 2019 +0200 @@ -17,18 +17,10 @@ # with CubicWeb. If not, see . """basic plot views""" - -from cubicweb import _ - -from six import add_metaclass -from six.moves import range - -from logilab.common.date import datetime2ticks -from logilab.common.deprecation import class_deprecated from logilab.common.registry import objectify_predicate from logilab.mtconverter import xml_escape -from cubicweb.utils import UStringIO, json_dumps +from cubicweb.utils import UStringIO from cubicweb.predicates import multi_columns_rset from cubicweb.web.views import baseviews @@ -87,89 +79,6 @@ raise NotImplementedError -@add_metaclass(class_deprecated) -class FlotPlotWidget(PlotWidget): - """PlotRenderer widget using Flot""" - __deprecation_warning__ = '[3.14] cubicweb.web.views.plots module is deprecated, use the jqplot cube instead' - onload = u""" -var fig = jQuery('#%(figid)s'); -if (fig.attr('cubicweb:type') != 'prepared-plot') { - %(plotdefs)s - jQuery.plot(jQuery('#%(figid)s'), [%(plotdata)s], - {points: {show: true}, - lines: {show: true}, - grid: {hoverable: true}, - /*yaxis : {tickFormatter : suffixFormatter},*/ - xaxis: {mode: %(mode)s}}); - jQuery('#%(figid)s').data({mode: %(mode)s, dateformat: %(dateformat)s}); - jQuery('#%(figid)s').bind('plothover', onPlotHover); - fig.attr('cubicweb:type','prepared-plot'); -} -""" - - def __init__(self, labels, plots, timemode=False): - self.labels = labels - self.plots = plots # list of list of couples - self.timemode = timemode - - def dump_plot(self, plot): - if self.timemode: - plot = [(datetime2ticks(x), y) for x, y in plot] - return json_dumps(plot) - - def _render(self, req, width=500, height=400): - if req.ie_browser(): - req.add_js('excanvas.js') - req.add_js(('jquery.flot.js', 'cubicweb.flot.js')) - figid = u'figure%s' % next(req.varmaker) - plotdefs = [] - plotdata = [] - self.w(u'
    ' % - (figid, width, height)) - for idx, (label, plot) in enumerate(zip(self.labels, self.plots)): - plotid = '%s_%s' % (figid, idx) - plotdefs.append('var %s = %s;' % (plotid, self.dump_plot(plot))) - # XXX ugly but required in order to not crash my demo - plotdata.append("{label: '%s', data: %s}" % (label.replace(u'&', u''), plotid)) - fmt = req.property_value('ui.date-format') # XXX datetime-format - # XXX TODO make plot options customizable - req.html_headers.add_onload(self.onload % - {'plotdefs': '\n'.join(plotdefs), - 'figid': figid, - 'plotdata': ','.join(plotdata), - 'mode': self.timemode and "'time'" or 'null', - 'dateformat': '"%s"' % fmt}) - - -@add_metaclass(class_deprecated) -class PlotView(baseviews.AnyRsetView): - __deprecation_warning__ = '[3.14] cubicweb.web.views.plots module is deprecated, use the jqplot cube instead' - __regid__ = 'plot' - title = _('generic plot') - __select__ = multi_columns_rset() & all_columns_are_numbers() - timemode = False - paginable = False - - def call(self, width=500, height=400): - # prepare data - rqlst = self.cw_rset.syntax_tree() - # XXX try to make it work with unions - varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:] - abscissa = [row[0] for row in self.cw_rset] - plots = [] - nbcols = len(self.cw_rset.rows[0]) - for col in range(1, nbcols): - data = [row[col] for row in self.cw_rset] - plots.append(filterout_nulls(abscissa, data)) - plotwidget = FlotPlotWidget(varnames, plots, timemode=self.timemode) - plotwidget.render(self._cw, width, height, w=self.w) - - -class TimeSeriePlotView(PlotView): - __select__ = multi_columns_rset() & columns_are_date_then_numbers() - timemode = True - - try: from GChartWrapper import Pie, Pie3D except ImportError: diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/primary.py --- a/cubicweb/web/views/primary.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/primary.py Fri May 24 16:29:14 2019 +0200 @@ -40,9 +40,6 @@ from cubicweb import _ -from warnings import warn - -from logilab.common.deprecation import deprecated from logilab.mtconverter import xml_escape from cubicweb import Unauthorized, NoSelectableObject diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/pyviews.py --- a/cubicweb/web/views/pyviews.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/pyviews.py Fri May 24 16:29:14 2019 +0200 @@ -18,10 +18,6 @@ """Basic views for python values (eg without any result set) """ - -from six import text_type -from six.moves import range - from cubicweb.view import View from cubicweb.predicates import match_kwargs from cubicweb.web.views import tableview @@ -41,7 +37,7 @@ w(self.empty_cell_content) def render_cell(self, w, rownum): - w(text_type(self.data[rownum][self.colid])) + w(str(self.data[rownum][self.colid])) class PyValTableView(tableview.TableMixIn, View): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/rdf.py --- a/cubicweb/web/views/rdf.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/rdf.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six.moves import range - from yams import xy from cubicweb.schema import VIRTUAL_RTYPES diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/reledit.py --- a/cubicweb/web/views/reledit.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/reledit.py Fri May 24 16:29:14 2019 +0200 @@ -23,10 +23,8 @@ from cubicweb import _ import copy -from warnings import warn from logilab.mtconverter import xml_escape -from logilab.common.deprecation import deprecated, class_renamed from logilab.common.decorators import cached from cubicweb import neg_role @@ -390,9 +388,6 @@ self._close_form_wrapper() -ClickAndEditFormView = class_renamed('ClickAndEditFormView', AutoClickAndEditFormView) - - @ajaxfunc(output_type='xhtml') def reledit_form(self): req = self._cw diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/schema.py --- a/cubicweb/web/views/schema.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/schema.py Fri May 24 16:29:14 2019 +0200 @@ -26,8 +26,6 @@ import os, os.path as osp import codecs -from six import text_type - from logilab.common.graph import GraphGenerator, DotBackend from logilab.common.ureports import Section, Table from logilab.common.registry import yes @@ -281,7 +279,7 @@ def cell_call(self, row, col): defaultval = self.cw_rset.rows[row][col] if defaultval is not None: - self.w(text_type(self.cw_rset.rows[row][col].unzpickle())) + self.w(str(self.cw_rset.rows[row][col].unzpickle())) class CWETypeRelationCardinalityCell(baseviews.FinalView): __regid__ = 'etype-rel-cardinality-cell' @@ -489,7 +487,7 @@ entity = self.cw_rset.get_entity(row, col) rschema = self._cw.vreg.schema.rschema(entity.rtype.name) rdef = rschema.rdefs[(entity.stype.name, entity.otype.name)] - constraints = [xml_escape(text_type(c)) for c in getattr(rdef, 'constraints')] + constraints = [xml_escape(str(c)) for c in getattr(rdef, 'constraints')] self.w(u'
    '.join(constraints)) class CWAttributeOptionsCell(EntityView): diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/sparql.py --- a/cubicweb/web/views/sparql.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/sparql.py Fri May 24 16:29:14 2019 +0200 @@ -20,8 +20,6 @@ from cubicweb import _ -from six.moves import range - from yams import xy from rql import TypeResolverException diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/startup.py --- a/cubicweb/web/views/startup.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/startup.py Fri May 24 16:29:14 2019 +0200 @@ -25,7 +25,6 @@ from cubicweb import _ from logilab.common.textutils import unormalize -from logilab.common.deprecation import deprecated from logilab.mtconverter import xml_escape from cubicweb.view import StartupView @@ -167,7 +166,3 @@ """ __regid__ = 'index' title = _('view_index') - - @deprecated('[3.11] display_folders method is deprecated, backport it if needed') - def display_folders(self): - return 'Folder' in self._cw.vreg.schema and self._cw.execute('Any COUNT(X) WHERE X is Folder')[0][0] diff -r ba5231e1aa45 -r 32ee89340e59 cubicweb/web/views/tableview.py --- a/cubicweb/web/views/tableview.py Tue May 21 10:50:08 2019 +0200 +++ b/cubicweb/web/views/tableview.py Fri May 24 16:29:14 2019 +0200 @@ -63,13 +63,9 @@ from cubicweb import _ -from warnings import warn from copy import copy from types import MethodType -from six import string_types, add_metaclass, create_bound_method -from six.moves import range - from logilab.mtconverter import xml_escape from logilab.common.decorators import cachedproperty from logilab.common.deprecation import class_deprecated @@ -287,7 +283,7 @@ attrs = renderer.attributes.copy() if renderer.sortable: sortvalue = renderer.sortvalue(rownum) - if isinstance(sortvalue, string_types): + if isinstance(sortvalue, str): sortvalue = sortvalue[:self.sortvalue_limit] if sortvalue is not None: attrs[u'cubicweb:sortvalue'] = js_dumps(sortvalue) @@ -605,7 +601,7 @@ return self.__regid__ == 'table' def call(self, headers=None, displaycols=None, cellvids=None, - paginate=None, **kwargs): + paginate=None): if self.headers: self.headers = [h and self._cw._(h) for h in self.headers] if (headers or displaycols or cellvids or paginate): @@ -617,15 +613,7 @@ self.cellvids = cellvids if paginate is not None: self.paginable = paginate - if kwargs: - # old table view arguments that we can safely ignore thanks to - # selectors - if len(kwargs) > 1: - msg = '[3.14] %s arguments are deprecated' % ', '.join(kwargs) - else: - msg = '[3.14] %s argument is deprecated' % ', '.join(kwargs) - warn(msg, DeprecationWarning, stacklevel=2) - super(RsetTableView, self).call(**kwargs) + super(RsetTableView, self).call() def main_var_index(self): """returns the index of the first non-attribute variable among the RQL @@ -726,7 +714,7 @@ for aname, member in[('renderfunc', renderfunc), ('sortfunc', sortfunc)]: if isinstance(member, MethodType): - member = create_bound_method(member.__func__, acopy) + member = MethodType(member.__func__, acopy) setattr(acopy, aname, member) return acopy finally: @@ -921,8 +909,7 @@ ################################################################################ -@add_metaclass(class_deprecated) -class TableView(AnyRsetView): +class TableView(AnyRsetView, metaclass=class_deprecated): """The table view accepts any non-empty rset. It uses introspection on the result set to compute column names and the proper way to display the cells. @@ -1187,8 +1174,7 @@ title = _('editable-table') -@add_metaclass(class_deprecated) -class CellView(EntityView): +class CellView(EntityView, metaclass=class_deprecated): __deprecation_warning__ = '[3.14] %(cls)s is deprecated' __regid__ = 'cell' __select__ = nonempty_rset() @@ -1211,128 +1197,3 @@ else: # XXX why do we need a fallback view here? self.wview(cellvid or 'final', self.cw_rset, 'null', row=row, col=col) - - -class InitialTableView(TableView): - """same display as table view but consider two rql queries : - - * the default query (ie `rql` form parameter), which is only used to select - this view and to build the filter form. This query should have the same - structure as the actual without actual restriction (but link to - restriction variables) and usually with a limit for efficiency (limit set - to 2 is advised) - - * the actual query (`actualrql` form parameter) whose results will be - displayed with default restrictions set - """ - __regid__ = 'initialtable' - __select__ = nonempty_rset() - # should not be displayed in possible view since it expects some specific - # parameters - title = None - - def call(self, title=None, subvid=None, headers=None, divid=None, - paginate=False, displaycols=None, displayactions=None, - mainindex=None): - """Dumps a table displaying a composite query""" - try: - actrql = self._cw.form['actualrql'] - except KeyError: - actrql = self.cw_rset.printable_rql() - else: - self._cw.ensure_ro_rql(actrql) - displaycols = self.displaycols(displaycols, headers) - if displayactions is None and 'displayactions' in self._cw.form: - displayactions = True - if divid is None and 'divid' in self._cw.form: - divid = self._cw.form['divid'] - self.w(u'
    ') - if not title and 'title' in self._cw.form: - # pop title so it's not displayed by the table view as well - title = self._cw.form.pop('title') - if title: - self.w(u'

    %s

    \n' % title) - if mainindex is None: - mainindex = self.main_var_index() - if mainindex is not None: - actions = self.form_filter(divid, displaycols, displayactions, - displayfilter=True, paginate=paginate, - hidden=True) - else: - actions = () - if not subvid and 'subvid' in self._cw.form: - subvid = self._cw.form.pop('subvid') - self._cw.view('table', self._cw.execute(actrql), - 'noresult', w=self.w, displayfilter=False, subvid=subvid, - displayactions=displayactions, displaycols=displaycols, - actions=actions, headers=headers, divid=divid) - self.w(u'
    \n') - - -class EditableInitialTableTableView(InitialTableView): - __regid__ = 'editable-initialtable' - finalview = 'editable-final' - - -@add_metaclass(class_deprecated) -class EntityAttributesTableView(EntityView): - """This table displays entity attributes in a table and allow to set a - specific method to help building cell content for each attribute as well as - column header. - - Table will render entity cell by using the appropriate build_COLNAME_cell - methods if defined otherwise cell content will be entity.COLNAME. - - Table will render column header using the method header_for_COLNAME if - defined otherwise COLNAME will be used. - """ - __deprecation_warning__ = '[3.14] %(cls)s is deprecated' - __abstract__ = True - columns = () - table_css = "listing" - css_files = () - - def call(self, columns=None): - if self.css_files: - self._cw.add_css(self.css_files) - _ = self._cw._ - self.columns = columns or self.columns - sample = self.cw_rset.get_entity(0, 0) - self.w(u'' % self.table_css) - self.table_header(sample) - self.w(u'') - for row in range(self.cw_rset.rowcount): - self.cell_call(row=row, col=0) - self.w(u'') - self.w(u'
    ') - - def cell_call(self, row, col): - _ = self._cw._ - entity = self.cw_rset.get_entity(row, col) - entity.complete() - infos = {} - for col in self.columns: - meth = getattr(self, 'build_%s_cell' % col, None) - # find the build method or try to find matching attribute - if meth: - content = meth(entity) - else: - content = entity.printable_value(col) - infos[col] = content - self.w(u"""
%%(%s)s
%s