Drop python2 support
authorDenis Laxalde <denis.laxalde@logilab.fr>
Fri, 05 Apr 2019 17:58:19 +0200
changeset 12567 26744ad37953
parent 12566 6b3523f81f42
child 12568 fc45f22c8100
Drop python2 support This mostly consists in removing the dependency on "six" and updating the code to use only Python3 idioms. Notice that we previously used TemporaryDirectory from cubicweb.devtools.testlib for compatibility with Python2. We now directly import it from tempfile.
cubicweb.spec
cubicweb/__init__.py
cubicweb/__pkginfo__.py
cubicweb/_exceptions.py
cubicweb/_gcdebug.py
cubicweb/crypto.py
cubicweb/cwconfig.py
cubicweb/cwctl.py
cubicweb/cwgettext.py
cubicweb/cwvreg.py
cubicweb/dataimport/csv.py
cubicweb/dataimport/massive_store.py
cubicweb/dataimport/pgstore.py
cubicweb/dataimport/stores.py
cubicweb/dataimport/test/test_pgstore.py
cubicweb/devtools/__init__.py
cubicweb/devtools/devctl.py
cubicweb/devtools/fake.py
cubicweb/devtools/fill.py
cubicweb/devtools/httptest.py
cubicweb/devtools/instrument.py
cubicweb/devtools/qunit.py
cubicweb/devtools/repotest.py
cubicweb/devtools/stresstester.py
cubicweb/devtools/test/unittest_dbfill.py
cubicweb/devtools/test/unittest_devctl.py
cubicweb/devtools/test/unittest_httptest.py
cubicweb/devtools/test/unittest_i18n.py
cubicweb/devtools/test/unittest_testlib.py
cubicweb/devtools/test/unittest_webtest.py
cubicweb/devtools/testlib.py
cubicweb/entities/__init__.py
cubicweb/entities/authobjs.py
cubicweb/entities/lib.py
cubicweb/entities/sources.py
cubicweb/entities/wfobjs.py
cubicweb/entity.py
cubicweb/ext/rest.py
cubicweb/ext/test/unittest_rest.py
cubicweb/hooks/integrity.py
cubicweb/hooks/test/unittest_hooks.py
cubicweb/hooks/test/unittest_syncsession.py
cubicweb/i18n.py
cubicweb/mail.py
cubicweb/md5crypt.py
cubicweb/migration.py
cubicweb/misc/migration/3.10.0_Any.py
cubicweb/misc/migration/3.15.0_Any.py
cubicweb/misc/migration/bootstrapmigration_repository.py
cubicweb/misc/migration/postcreate.py
cubicweb/misc/scripts/migration_helper.py
cubicweb/multipart.py
cubicweb/predicates.py
cubicweb/pyramid/__init__.py
cubicweb/pyramid/profile.py
cubicweb/pyramid/pyramidctl.py
cubicweb/pyramid/resources.py
cubicweb/pyramid/test/test_config.py
cubicweb/pyramid/test/test_hooks.py
cubicweb/req.py
cubicweb/rqlrewrite.py
cubicweb/rset.py
cubicweb/rtags.py
cubicweb/schema.py
cubicweb/server/__init__.py
cubicweb/server/checkintegrity.py
cubicweb/server/hook.py
cubicweb/server/migractions.py
cubicweb/server/querier.py
cubicweb/server/repository.py
cubicweb/server/rqlannotation.py
cubicweb/server/schema2sql.py
cubicweb/server/schemaserial.py
cubicweb/server/serverconfig.py
cubicweb/server/serverctl.py
cubicweb/server/session.py
cubicweb/server/sources/__init__.py
cubicweb/server/sources/datafeed.py
cubicweb/server/sources/ldapfeed.py
cubicweb/server/sources/native.py
cubicweb/server/sources/rql2sql.py
cubicweb/server/sources/storages.py
cubicweb/server/sqlutils.py
cubicweb/server/ssplanner.py
cubicweb/server/test/unittest_checkintegrity.py
cubicweb/server/test/unittest_ldapsource.py
cubicweb/server/test/unittest_migractions.py
cubicweb/server/test/unittest_postgres.py
cubicweb/server/test/unittest_querier.py
cubicweb/server/test/unittest_repository.py
cubicweb/server/test/unittest_rql2sql.py
cubicweb/server/test/unittest_security.py
cubicweb/server/test/unittest_storage.py
cubicweb/server/test/unittest_undo.py
cubicweb/server/test/unittest_utils.py
cubicweb/server/utils.py
cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl
cubicweb/sobjects/ldapparser.py
cubicweb/sobjects/notification.py
cubicweb/sobjects/services.py
cubicweb/test/unittest_binary.py
cubicweb/test/unittest_cwconfig.py
cubicweb/test/unittest_cwctl.py
cubicweb/test/unittest_entity.py
cubicweb/test/unittest_predicates.py
cubicweb/test/unittest_rqlrewrite.py
cubicweb/test/unittest_rset.py
cubicweb/test/unittest_uilib.py
cubicweb/test/unittest_utils.py
cubicweb/toolsutils.py
cubicweb/uilib.py
cubicweb/utils.py
cubicweb/view.py
cubicweb/web/__init__.py
cubicweb/web/_exceptions.py
cubicweb/web/application.py
cubicweb/web/box.py
cubicweb/web/captcha.py
cubicweb/web/component.py
cubicweb/web/controller.py
cubicweb/web/cors.py
cubicweb/web/facet.py
cubicweb/web/form.py
cubicweb/web/formfields.py
cubicweb/web/formwidgets.py
cubicweb/web/htmlwidgets.py
cubicweb/web/http_headers.py
cubicweb/web/request.py
cubicweb/web/schemaviewer.py
cubicweb/web/test/data/cubicweb_file/entities.py
cubicweb/web/test/unittest_application.py
cubicweb/web/test/unittest_form.py
cubicweb/web/test/unittest_magicsearch.py
cubicweb/web/test/unittest_request.py
cubicweb/web/test/unittest_urlrewrite.py
cubicweb/web/test/unittest_views_basecontrollers.py
cubicweb/web/test/unittest_views_json.py
cubicweb/web/test/unittest_viewselector.py
cubicweb/web/uihelper.py
cubicweb/web/views/ajaxcontroller.py
cubicweb/web/views/authentication.py
cubicweb/web/views/autoform.py
cubicweb/web/views/basecontrollers.py
cubicweb/web/views/baseviews.py
cubicweb/web/views/boxes.py
cubicweb/web/views/csvexport.py
cubicweb/web/views/cwsources.py
cubicweb/web/views/cwuser.py
cubicweb/web/views/debug.py
cubicweb/web/views/editcontroller.py
cubicweb/web/views/editforms.py
cubicweb/web/views/formrenderers.py
cubicweb/web/views/forms.py
cubicweb/web/views/ibreadcrumbs.py
cubicweb/web/views/idownloadable.py
cubicweb/web/views/magicsearch.py
cubicweb/web/views/navigation.py
cubicweb/web/views/owl.py
cubicweb/web/views/pyviews.py
cubicweb/web/views/rdf.py
cubicweb/web/views/schema.py
cubicweb/web/views/sparql.py
cubicweb/web/views/tableview.py
cubicweb/web/views/tabs.py
cubicweb/web/views/timetable.py
cubicweb/web/views/uicfg.py
cubicweb/web/views/urlrewrite.py
cubicweb/web/views/wdoc.py
cubicweb/web/views/workflow.py
cubicweb/web/views/xbel.py
cubicweb/web/views/xmlrss.py
cubicweb/web/webconfig.py
cubicweb/web/webctl.py
cubicweb/wfutils.py
cubicweb/wsgi/__init__.py
cubicweb/wsgi/handler.py
cubicweb/wsgi/request.py
debian/control
doc/changes/3.27.rst
doc/tools/mode_plan.py
setup.py
tox.ini
--- a/cubicweb.spec	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb.spec	Fri Apr 05 17:58:19 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,7 +28,6 @@
 Requires:       %{python}-logilab-database >= 1.15.0
 Requires:       %{python}-passlib
 Requires:       %{python}-lxml
-Requires:       %{python}-unittest2 >= 0.7.0
 Requires:       %{python}-markdown
 Requires:       pytz
 # the schema view uses `dot'; at least on el5, png output requires graphviz-gd
--- a/cubicweb/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -27,8 +27,6 @@
 import warnings
 import zlib
 
-from six import PY2, binary_type, text_type
-
 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
@@ -40,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,
@@ -63,12 +57,12 @@
 
 # '_' is available to mark internationalized string but should not be used to
 # do the actual translation
-_ = text_type
+_ = 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), \
@@ -144,7 +138,7 @@
 
 
 def check_password(eschema, value):
-    return isinstance(value, (binary_type, Binary))
+    return isinstance(value, (bytes, Binary))
 
 
 BASE_CHECKERS['Password'] = check_password
@@ -153,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
--- a/cubicweb/__pkginfo__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/__pkginfo__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -34,7 +34,7 @@
 classifiers = [
     'Environment :: Web Environment',
     'Framework :: CubicWeb',
-    'Programming Language :: Python',
+    'Programming Language :: Python :: 3',
     'Programming Language :: JavaScript',
 ]
 
--- a/cubicweb/_exceptions.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/_exceptions.py	Fri Apr 05 17:58:19 2019 +0200
@@ -21,8 +21,6 @@
 
 from warnings import warn
 
-from six import PY2, text_type
-
 from logilab.common.decorators import cachedproperty
 
 from yams import ValidationError
@@ -32,21 +30,15 @@
 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"""
@@ -98,7 +90,7 @@
     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)
 
@@ -119,7 +111,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
@@ -127,7 +119,7 @@
                 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
--- a/cubicweb/_gcdebug.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/_gcdebug.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
-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,)
--- a/cubicweb/crypto.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/crypto.py	Fri Apr 05 17:58:19 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
 
@@ -38,7 +37,7 @@
     string = pickle.dumps(data)
     string = string + '*' * (8 - len(string) % 8)
     string = b64encode(_cypherer(seed).encrypt(string))
-    return unicode(string)
+    return str(string)
 
 
 def decrypt(string, seed):
--- a/cubicweb/cwconfig.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/cwconfig.py	Fri Apr 05 17:58:19 2019 +0200
@@ -166,8 +166,6 @@
    Directory where pid files will be written
 """
 
-from __future__ import print_function
-
 import importlib
 import logging
 import logging.config
@@ -182,8 +180,6 @@
 from threading import Lock
 from warnings import filterwarnings
 
-from six import text_type
-
 from logilab.common.decorators import cached
 from logilab.common.logging_ext import set_log_methods, init_log
 from logilab.common.configuration import (Configuration, Method,
@@ -641,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.
@@ -990,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
--- a/cubicweb/cwctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/cwctl.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,8 +18,6 @@
 """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.
--- a/cubicweb/cwgettext.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/cwgettext.py	Fri Apr 05 17:58:19 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
 
 
--- a/cubicweb/cwvreg.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/cwvreg.py	Fri Apr 05 17:58:19 2019 +0200
@@ -24,8 +24,6 @@
 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
@@ -218,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):
--- a/cubicweb/dataimport/csv.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/dataimport/csv.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """Functions to help importing CSV data"""
-from __future__ import absolute_import, print_function
-
 import codecs
 import csv as csvmod
 
-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
@@ -42,7 +38,7 @@
 def ucsvreader_pb(stream_or_path, encoding='utf-8', delimiter=',', quotechar='"',
                   skipfirst=False, withpb=True, skip_empty=True):
     """same as :func:`ucsvreader` but a progress bar is displayed as we iter on rows"""
-    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,19 +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)
+    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:
@@ -97,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
--- a/cubicweb/dataimport/massive_store.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/dataimport/massive_store.py	Fri Apr 05 17:58:19 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)
--- a/cubicweb/dataimport/pgstore.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/dataimport/pgstore.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,17 +17,13 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
+import pickle
 
 from cubicweb.utils import make_uid
 from cubicweb.server.sqlutils import SQL_PREFIX
@@ -108,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.
@@ -140,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),
@@ -185,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))
--- a/cubicweb/dataimport/stores.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/dataimport/stores.py	Fri Apr 05 17:58:19 2019 +0200
@@ -62,8 +62,6 @@
 from copy import copy
 from itertools import count
 
-from six import add_metaclass
-
 import pytz
 
 from logilab.common.decorators import cached
@@ -362,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.
 
--- a/cubicweb/dataimport/test/test_pgstore.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/dataimport/test/test_pgstore.py	Fri Apr 05 17:58:19 2019 +0200
@@ -20,16 +20,11 @@
 
 import datetime as DT
 
-from six import PY3
 from logilab.common.testlib import TestCase, unittest_main
 
 from cubicweb.dataimport import pgstore
 
 
-if PY3:
-    long = int
-
-
 class CreateCopyFromBufferTC(TestCase):
 
     # test converters
@@ -41,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):
@@ -68,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
--- a/cubicweb/devtools/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Test tools for cubicweb"""
 
-from __future__ import print_function
-
 import os
 import sys
 import errno
@@ -31,10 +29,9 @@
 from hashlib import sha1  # pylint: disable=E0611
 from os.path import abspath, join, exists, split, isdir, dirname
 from functools import partial
+import pickle
 
 import filelock
-from six import text_type
-from six.moves import cPickle as pickle
 
 from logilab.common.decorators import cached, clear_cache
 
@@ -93,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'
@@ -395,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
--- a/cubicweb/devtools/devctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/devctl.py	Fri Apr 05 17:58:19 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.
@@ -33,8 +31,6 @@
 
 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
@@ -717,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:
--- a/cubicweb/devtools/fake.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/fake.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/devtools/fill.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/fill.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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
@@ -270,8 +264,7 @@
         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
 
 
--- a/cubicweb/devtools/httptest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/httptest.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,15 +18,12 @@
 """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.moves import range, http_client
-from six.moves.urllib.parse import urlparse
-
+from urllib.parse import urlparse
 
 from cubicweb.devtools.testlib import CubicWebTC
 
@@ -83,7 +80,7 @@
             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,7 +92,7 @@
         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.
         """
@@ -131,7 +128,7 @@
     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
@@ -164,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
 
--- a/cubicweb/devtools/instrument.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/instrument.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """Instrumentation utilities"""
-from __future__ import print_function
-
 import os
 
 try:
--- a/cubicweb/devtools/qunit.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/qunit.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
-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):
--- a/cubicweb/devtools/repotest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/repotest.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/devtools/stresstester.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/stresstester.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/devtools/test/unittest_dbfill.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_dbfill.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/devtools/test/unittest_devctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_devctl.py	Fri Apr 05 17:58:19 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]
--- a/cubicweb/devtools/test/unittest_httptest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_httptest.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,7 +17,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
@@ -28,12 +28,12 @@
     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'):
@@ -47,20 +47,20 @@
 
     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)
+        self.assertEqual(response.status, http.client.FORBIDDEN, response.body)
 
 
 if __name__ == '__main__':
--- a/cubicweb/devtools/test/unittest_i18n.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_i18n.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,14 +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 cubicweb.devtools import devctl
@@ -91,7 +90,7 @@
 
 @contextmanager
 def capture_stdout():
-    stream = BytesIO() if PY2 else StringIO()
+    stream = StringIO()
     sys.stdout = stream
     yield stream
     stream.seek(0)
--- a/cubicweb/devtools/test/unittest_testlib.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_testlib.py	Fri Apr 05 17:58:19 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):
--- a/cubicweb/devtools/test/unittest_webtest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/test/unittest_webtest.py	Fri Apr 05 17:58:19 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__':
--- a/cubicweb/devtools/testlib.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/devtools/testlib.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,20 +17,15 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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 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
 
@@ -53,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 ##################################
 
@@ -326,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
@@ -347,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 ########################################################
@@ -365,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',
@@ -458,15 +436,13 @@
         """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:
@@ -524,7 +500,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]
@@ -730,7 +706,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)
@@ -911,7 +887,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):
@@ -944,7 +920,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)
@@ -977,8 +953,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
--- a/cubicweb/entities/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entities/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """base application's entities class implementation: `AnyEntity`"""
 
-from six import text_type, string_types
-
 from logilab.common.decorators import classproperty
 
 from cubicweb import Unauthorized
@@ -34,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):
@@ -111,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
 
--- a/cubicweb/entities/authobjs.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entities/authobjs.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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)
--- a/cubicweb/entities/lib.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entities/lib.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/entities/sources.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entities/sources.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/entities/wfobjs.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entities/wfobjs.py	Fri Apr 05 17:58:19 2019 +0200
@@ -21,12 +21,6 @@
 * 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 cubicweb.entities import AnyEntity, fetch_config
@@ -98,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
@@ -115,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:
@@ -127,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})
@@ -257,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
@@ -415,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)
@@ -480,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):
@@ -529,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)
--- a/cubicweb/entity.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/entity.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,9 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Base class for entity objects manipulated in clients"""
 
-from six import text_type, string_types, integer_types
-from six.moves import range
-
 from logilab.common.decorators import cached
 from logilab.common.registry import yes
 from logilab.mtconverter import TransformData, xml_escape
@@ -55,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:
@@ -265,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
@@ -506,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__()
 
@@ -639,7 +635,7 @@
         if path is None:
             # fallback url: <base-url>/<eid> url is used as cw entities uri,
             # prefer it to <base-url>/<etype>/eid/<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):
@@ -657,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''
--- a/cubicweb/ext/rest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/ext/rest.py	Fri Apr 05 17:58:19 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)
 
--- a/cubicweb/ext/test/unittest_rest.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/ext/test/unittest_rest.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
-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("<p>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):
--- a/cubicweb/hooks/integrity.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/hooks/integrity.py	Fri Apr 05 17:58:19 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)
 
--- a/cubicweb/hooks/test/unittest_hooks.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/hooks/test/unittest_hooks.py	Fri Apr 05 17:58:19 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',
--- a/cubicweb/hooks/test/unittest_syncsession.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/hooks/test/unittest_syncsession.py	Fri Apr 05 17:58:19 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'})
 
--- a/cubicweb/i18n.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/i18n.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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:
--- a/cubicweb/mail.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/mail.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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)
--- a/cubicweb/md5crypt.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/md5crypt.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/migration.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/migration.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """utilities for instances migration"""
-from __future__ import print_function
 
 import sys
 import os
@@ -25,8 +24,6 @@
 from os.path import exists, join, basename, splitext
 from itertools import chain
 
-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
@@ -405,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)
--- a/cubicweb/misc/migration/3.10.0_Any.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/misc/migration/3.10.0_Any.py	Fri Apr 05 17:58:19 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()
 
--- a/cubicweb/misc/migration/3.15.0_Any.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/misc/migration/3.15.0_Any.py	Fri Apr 05 17:58:19 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)
 
 
--- a/cubicweb/misc/migration/bootstrapmigration_repository.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/misc/migration/bootstrapmigration_repository.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/misc/migration/postcreate.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/misc/migration/postcreate.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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():
--- a/cubicweb/misc/scripts/migration_helper.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/misc/scripts/migration_helper.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/multipart.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/multipart.py	Fri Apr 05 17:58:19 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 ################################
@@ -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. '''
@@ -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:
--- a/cubicweb/predicates.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/predicates.py	Fri Apr 05 17:58:19 2019 +0200
@@ -24,9 +24,6 @@
 from warnings import warn
 from operator import eq
 
-from six import string_types, integer_types
-from six.moves import range
-
 from logilab.common.registry import Predicate, objectify_predicate
 
 from yams.schema import BASE_TYPES, role_name
@@ -610,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__,
@@ -670,7 +667,7 @@
             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
@@ -1088,7 +1085,7 @@
 
     See :class:`cubicweb.entities.wfobjs.TrInfo` for more information.
     """
-    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
@@ -1288,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)
--- a/cubicweb/pyramid/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -21,10 +21,10 @@
 """Pyramid interface to CubicWeb"""
 
 import atexit
+from configparser import SafeConfigParser
 import os
 import warnings
 
-from six.moves.configparser import SafeConfigParser
 import wsgicors
 
 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
--- a/cubicweb/pyramid/profile.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/profile.py	Fri Apr 05 17:58:19 2019 +0200
@@ -21,7 +21,6 @@
 """ Tools for profiling.
 
 See :ref:`profiling`."""
-from __future__ import print_function
 
 import cProfile
 import itertools
--- a/cubicweb/pyramid/pyramidctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/pyramidctl.py	Fri Apr 05 17:58:19 2019 +0200
@@ -25,8 +25,6 @@
 the pyramid script 'pserve'.
 """
 
-from __future__ import print_function
-
 import atexit
 import errno
 import os
--- a/cubicweb/pyramid/resources.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/resources.py	Fri Apr 05 17:58:19 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
 
 
--- a/cubicweb/pyramid/test/test_config.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/test/test_config.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,11 +19,11 @@
 
 import os
 from os import path
+from tempfile import TemporaryDirectory
 from unittest import TestCase
 
 from mock import patch
 
-from cubicweb.devtools.testlib import TemporaryDirectory
 
 from cubicweb.pyramid import config
 
--- a/cubicweb/pyramid/test/test_hooks.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/pyramid/test/test_hooks.py	Fri Apr 05 17:58:19 2019 +0200
@@ -1,5 +1,3 @@
-from six import text_type
-
 from cubicweb.pyramid.test import PyramidCWTest
 from cubicweb.pyramid import tools
 
@@ -11,10 +9,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 +27,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
 
 
--- a/cubicweb/req.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/req.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,11 +18,9 @@
 """Base class for request/session"""
 
 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.date import ustrftime, strptime, todate, todatetime
@@ -73,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
@@ -97,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
@@ -274,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):
@@ -285,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
@@ -314,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
@@ -386,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):
--- a/cubicweb/rqlrewrite.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/rqlrewrite.py	Fri Apr 05 17:58:19 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)
--- a/cubicweb/rset.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/rset.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,10 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """The `ResultSet` class which is returned as result of an rql query"""
 
-
-from six import PY3, text_type
-from six.moves import range
-
 from logilab.common.decorators import cached, clear_cache, copy_cache
 from rql import nodes, stmts
 
@@ -366,13 +362,7 @@
         """return the result set's origin rql as a string, with arguments
         substitued
         """
-        encoding = self.req.encoding
-        rqlstr = self.syntax_tree().as_string(kwargs=self.args)
-        if PY3:
-            return rqlstr
-        if isinstance(rqlstr, text_type):
-            return rqlstr
-        return text_type(rqlstr, encoding)
+        return self.syntax_tree().as_string(kwargs=self.args)
 
     # client helper methods ###################################################
 
--- a/cubicweb/rtags.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/rtags.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/schema.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/schema.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,17 +17,12 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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 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.textutils import splitstrip
@@ -145,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]
 
 
@@ -218,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)
@@ -579,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):
@@ -627,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 ()
 
@@ -646,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 ()
 
@@ -1333,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.
--- a/cubicweb/server/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/__init__.py	Fri Apr 05 17:58:19 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})
--- a/cubicweb/server/checkintegrity.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/checkintegrity.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/server/hook.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/hook.py	Fri Apr 05 17:58:19 2019 +0200
@@ -245,7 +245,6 @@
 .. autoclass:: cubicweb.server.hook.LateOperation
 .. autoclass:: cubicweb.server.hook.DataOperationMixIn
 """
-from __future__ import print_function
 
 from logging import getLogger
 from itertools import chain
--- a/cubicweb/server/migractions.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/migractions.py	Fri Apr 05 17:58:19 2019 +0200
@@ -26,9 +26,6 @@
 * add an entity
 * execute raw RQL queries
 """
-from __future__ import print_function
-
-
 
 import sys
 import os
@@ -41,8 +38,6 @@
 from copy import copy
 from contextlib import contextmanager
 
-from six import PY2, text_type
-
 from logilab.common.decorators import cached, clear_cache
 
 from yams.buildobjs import EntityType
@@ -153,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):
@@ -1005,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()
@@ -1217,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:
@@ -1250,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,'
@@ -1287,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,)
@@ -1297,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
@@ -1340,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)
 
--- a/cubicweb/server/querier.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/querier.py	Fri Apr 05 17:58:19 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
@@ -442,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:
@@ -623,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
--- a/cubicweb/server/repository.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/repository.py	Fri Apr 05 17:58:19 2019 +0200
@@ -26,13 +26,10 @@
 * handles session management
 """
 
-from __future__ import print_function
-
 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
 
--- a/cubicweb/server/rqlannotation.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/rqlannotation.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,8 +19,6 @@
 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
--- a/cubicweb/server/schema2sql.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/schema2sql.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,9 +19,6 @@
 
 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
@@ -88,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):
@@ -204,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?
--- a/cubicweb/server/schemaserial.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/schemaserial.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,14 +17,10 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
@@ -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
--- a/cubicweb/server/serverconfig.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/serverconfig.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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
 
--- a/cubicweb/server/serverctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/serverctl.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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
 
@@ -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
 
 
--- a/cubicweb/server/session.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/session.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,16 +17,12 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Repository users' and internal' sessions."""
 
-from __future__ import print_function
-
 import functools
 import sys
 from uuid import uuid4
 from contextlib import contextmanager
 from logging import getLogger
 
-from six import text_type
-
 from logilab.common.registry import objectify_predicate
 
 from cubicweb import QueryError, ProgrammingError, schema, server
@@ -641,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
 
--- a/cubicweb/server/sources/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,13 +17,9 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
--- a/cubicweb/server/sources/datafeed.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/datafeed.py	Fri Apr 05 17:58:19 2019 +0200
@@ -24,11 +24,10 @@
 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
--- a/cubicweb/server/sources/ldapfeed.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/ldapfeed.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/server/sources/native.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/native.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,21 +17,17 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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"""
@@ -832,7 +827,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
 
@@ -907,7 +902,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
@@ -918,10 +913,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'"
@@ -955,11 +950,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:
@@ -1092,8 +1086,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
@@ -1133,14 +1125,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,
@@ -1215,7 +1207,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:
--- a/cubicweb/server/sources/rql2sql.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/rql2sql.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/server/sources/storages.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sources/storages.py	Fri Apr 05 17:58:19 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:
--- a/cubicweb/server/sqlutils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/sqlutils.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """SQL utilities functions and classes."""
 
-from __future__ import print_function
-
 import os
 import sys
 import re
@@ -27,9 +25,6 @@
 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
@@ -52,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:
@@ -81,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:
@@ -475,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:
@@ -484,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')
@@ -517,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)
 
--- a/cubicweb/server/ssplanner.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/ssplanner.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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:
--- a/cubicweb/server/test/unittest_checkintegrity.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_checkintegrity.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 
+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
--- a/cubicweb/server/test/unittest_ldapsource.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_ldapsource.py	Fri Apr 05 17:58:19 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))
--- a/cubicweb/server/test/unittest_migractions.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_migractions.py	Fri Apr 05 17:58:19 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,7 @@
 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
--- a/cubicweb/server/test/unittest_postgres.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_postgres.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/server/test/unittest_querier.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_querier.py	Fri Apr 05 17:58:19 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:
@@ -265,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})
@@ -327,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", '
@@ -903,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',))))
@@ -1073,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):
@@ -1282,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)
 
@@ -1332,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):
@@ -1379,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)})
@@ -1396,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)})
--- a/cubicweb/server/test/unittest_repository.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_repository.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/server/test/unittest_rql2sql.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_rql2sql.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.server.sources.rql2sql"""
-from __future__ import print_function
 
 import sys
 import unittest
--- a/cubicweb/server/test/unittest_security.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_security.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """functional tests for server'security"""
 
-from six.moves import range
-
 from logilab.common.testlib import unittest_main
 
 from cubicweb.devtools.testlib import CubicWebTC
--- a/cubicweb/server/test/unittest_storage.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_storage.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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:
--- a/cubicweb/server/test/unittest_undo.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_undo.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 
-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__':
--- a/cubicweb/server/test/unittest_utils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/test/unittest_utils.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,6 +17,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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):
--- a/cubicweb/server/utils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/server/utils.py	Fri Apr 05 17:58:19 2019 +0200
@@ -16,19 +16,12 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
 
@@ -92,8 +85,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:
@@ -106,22 +97,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
--- a/cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl	Fri Apr 05 17:58:19 2019 +0200
@@ -20,6 +20,6 @@
 classifiers = [
     'Environment :: Web Environment',
     'Framework :: CubicWeb',
-    'Programming Language :: Python',
+    'Programming Language :: Python :: 3',
     'Programming Language :: JavaScript',
 ]
--- a/cubicweb/sobjects/ldapparser.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/sobjects/ldapparser.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/sobjects/notification.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/sobjects/notification.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/sobjects/services.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/sobjects/services.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/test/unittest_binary.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_binary.py	Fri Apr 05 17:58:19 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')
--- a/cubicweb/test/unittest_cwconfig.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_cwconfig.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,22 +18,21 @@
 """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 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)
 
@@ -268,13 +267,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')), [])
--- a/cubicweb/test/unittest_cwctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_cwctl.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,11 +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 cubicweb.cwctl import ListCommand
@@ -38,7 +36,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):
--- a/cubicweb/test/unittest_entity.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_entity.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/test/unittest_predicates.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_predicates.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/test/unittest_rqlrewrite.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_rqlrewrite.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 
-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)
--- a/cubicweb/test/unittest_rset.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_rset.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,9 +18,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
 
@@ -655,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):
--- a/cubicweb/test/unittest_uilib.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_uilib.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/test/unittest_utils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/test/unittest_utils.py	Fri Apr 05 17:58:19 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
@@ -419,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):
--- a/cubicweb/toolsutils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/toolsutils.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 """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):
--- a/cubicweb/uilib.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/uilib.py	Fri Apr 05 17:58:19 2019 +0200
@@ -28,8 +28,6 @@
 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
 
@@ -64,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'))
@@ -94,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
@@ -124,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,
@@ -332,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):
@@ -347,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):
@@ -387,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)
 
@@ -405,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</%s>' % (content, tag)
     else:
         if tag in HTML4_EMPTY_TAGS:
@@ -434,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
 
@@ -443,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)
 
@@ -464,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:
@@ -501,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'&#160;&#160;%s<br/>\n' % (string))
         # add locals info for each entry
         try:
@@ -545,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:
@@ -570,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
@@ -579,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
--- a/cubicweb/utils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/utils.py	Fri Apr 05 17:58:19 2019 +0200
@@ -25,21 +25,13 @@
 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 threading import Lock
 from logging import getLogger
 
-from six import text_type
-
 from logilab.mtconverter import xml_escape
 from logilab.common.date import ustrftime
 
@@ -106,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
 
@@ -122,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('<', '</', 1))
 
@@ -204,8 +196,6 @@
     __nonzero__ = __bool__
 
     def write(self, value):
-        assert isinstance(value, text_type), u"unicode required not %s : %s"\
-                                     % (type(value).__name__, repr(value))
         if self.tracewrites:
             from traceback import format_stack
             stack = format_stack(None)[:-1]
--- a/cubicweb/view.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/view.py	Fri Apr 05 17:58:19 2019 +0200
@@ -24,8 +24,6 @@
 from warnings import warn
 from functools import partial
 
-from six.moves import range
-
 from logilab.common.registry import yes
 from logilab.mtconverter import xml_escape
 
--- a/cubicweb/web/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/__init__.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,11 +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 cubicweb.web._exceptions import *
 from cubicweb.utils import json_dumps
 from cubicweb.uilib import eid_param
--- a/cubicweb/web/_exceptions.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/_exceptions.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,9 +18,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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
--- a/cubicweb/web/application.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/application.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,14 +19,12 @@
 
 
 import contextlib
+import http.client as http_client
 import json
 import sys
 from time import clock, time
 from contextlib import contextmanager
 
-from six import text_type, binary_type
-from six.moves import http_client
-
 from rql import BadRQLQuery
 
 from cubicweb import set_log_methods
@@ -316,7 +314,7 @@
             # 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
 
     def core_handle(self, req):
@@ -481,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
--- a/cubicweb/web/box.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/box.py	Fri Apr 05 17:58:19 2019 +0200
@@ -20,8 +20,6 @@
 
 from cubicweb import _
 
-from six import add_metaclass
-
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import class_deprecated
 
@@ -58,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.
--- a/cubicweb/web/captcha.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/captcha.py	Fri Apr 05 17:58:19 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
 
 
--- a/cubicweb/web/component.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/component.py	Fri Apr 05 17:58:19 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'[<a href="javascript: %s" class="action">%s</a>] %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'<table><tr><td>')
-            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:
 
--- a/cubicweb/web/controller.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/controller.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,10 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract controller classe for CubicWeb web client"""
 
-
-
-from six import PY2
-
 from logilab.mtconverter import xml_escape
 from logilab.common.registry import yes
 
@@ -88,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)
--- a/cubicweb/web/cors.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/cors.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/facet.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/facet.py	Fri Apr 05 17:58:19 2019 +0200
@@ -56,8 +56,6 @@
 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
@@ -645,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 []
@@ -696,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:
@@ -720,7 +718,7 @@
         # 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(
@@ -884,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]
@@ -1037,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
@@ -1090,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 []
@@ -1113,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:
@@ -1217,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):
@@ -1236,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:
@@ -1337,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 []
 
 
@@ -1356,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:
@@ -1480,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 ################################################################
@@ -1559,7 +1557,7 @@
         if selected:
             cssclass += ' facetValueSelected'
         w(u'<div class="%s" cubicweb:value="%s">\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'&#160;' * self.scrollbar_padding_factor if overflow else u''
@@ -1718,7 +1716,7 @@
             imgsrc = self._cw.data_url(self.unselected_img)
             imgalt = self._cw._('not selected')
         w(u'<div class="%s" cubicweb:value="%s">\n'
-          % (cssclass, xml_escape(text_type(self.value))))
+          % (cssclass, xml_escape(str(self.value))))
         w(u'<div>')
         w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" />&#160;' % (imgsrc, imgalt))
         w(u'<label class="facetTitle" cubicweb:facetName="%s">%s</label>'
--- a/cubicweb/web/form.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/form.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,9 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """abstract form classes for CubicWeb web client"""
 
-
-from six import add_metaclass
-
 from logilab.common.decorators import iclassmethod
 
 from cubicweb.appobject import AppObject
@@ -73,8 +70,7 @@
     found
     """
 
-@add_metaclass(metafieldsform)
-class Form(AppObject):
+class Form(AppObject, metaclass=metafieldsform):
     __registry__ = 'forms'
 
     parent_form = None
--- a/cubicweb/web/formfields.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/formfields.py	Fri Apr 05 17:58:19 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"""
--- a/cubicweb/web/formwidgets.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/formwidgets.py	Fri Apr 05 17:58:19 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'</optgroup>')
         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')):
--- a/cubicweb/web/htmlwidgets.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/htmlwidgets.py	Fri Apr 05 17:58:19 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'</div>')
 
 
-@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'</ul></div></div>')
 
 
-@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'</ul><hr class="boxSeparator"/><ul>')
 
 
-@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'<li class="%s">%s</li>\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):
--- a/cubicweb/web/http_headers.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/http_headers.py	Fri Apr 05 17:58:19 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)
 
--- a/cubicweb/web/request.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/request.py	Fri Apr 05 17:58:19 2019 +0200
@@ -23,12 +23,10 @@
 from hashlib import sha1  # pylint: disable=E0611
 from calendar import timegm
 from datetime import date, datetime
+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
 
@@ -208,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':
@@ -274,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
 
@@ -287,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
@@ -374,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:
@@ -523,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:
@@ -543,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():
@@ -613,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"""
@@ -666,9 +660,9 @@
             # 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
--- a/cubicweb/web/schemaviewer.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/schemaviewer.py	Fri Apr 05 17:58:19 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)
--- a/cubicweb/web/test/data/cubicweb_file/entities.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/data/cubicweb_file/entities.py	Fri Apr 05 17:58:19 2019 +0200
@@ -1,4 +1,3 @@
-from six import text_type
 from logilab.mtconverter import guess_mimetype_and_encoding
 from cubicweb.entities import AnyEntity, fetch_config
 
@@ -42,9 +41,9 @@
                 format=format, encoding=encoding,
                 fallbackencoding=self._cw.encoding)
             if format:
-                self.cw_edited['data_format'] = text_type(format)
+                self.cw_edited['data_format'] = str(format)
             if encoding:
-                self.cw_edited['data_encoding'] = text_type(encoding)
+                self.cw_edited['data_encoding'] = str(encoding)
 
 
 class UnResizeable(Exception):
--- a/cubicweb/web/test/unittest_application.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_application.py	Fri Apr 05 17:58:19 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):
--- a/cubicweb/web/test/unittest_form.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_form.py	Fri Apr 05 17:58:19 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:
--- a/cubicweb/web/test/unittest_magicsearch.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_magicsearch.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/test/unittest_request.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_request.py	Fri Apr 05 17:58:19 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.
--- a/cubicweb/web/test/unittest_urlrewrite.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_urlrewrite.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
 
-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)
--- a/cubicweb/web/test/unittest_views_basecontrollers.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_views_basecontrollers.py	Fri Apr 05 17:58:19 2019 +0200
@@ -18,9 +18,7 @@
 """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
 
@@ -92,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',
@@ -149,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,
@@ -172,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',
@@ -192,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',
                 }
@@ -220,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',
@@ -287,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',
@@ -300,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',
@@ -320,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',
@@ -342,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',
@@ -351,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',
@@ -375,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)
 
@@ -388,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:
@@ -400,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
@@ -419,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',
@@ -430,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',
@@ -446,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
@@ -554,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')
@@ -563,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': ''}
@@ -672,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',
--- a/cubicweb/web/test/unittest_views_json.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_views_json.py	Fri Apr 05 17:58:19 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 <http://www.gnu.org/licenses/>.
-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
--- a/cubicweb/web/test/unittest_viewselector.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/test/unittest_viewselector.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,7 +17,6 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """XXX rename, split, reorganize this"""
-from __future__ import print_function
 
 from logilab.common.testlib import unittest_main
 
--- a/cubicweb/web/uihelper.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/uihelper.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/ajaxcontroller.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/ajaxcontroller.py	Fri Apr 05 17:58:19 2019 +0200
@@ -61,13 +61,9 @@
 
 """
 
-
-
+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
 
@@ -151,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
 
--- a/cubicweb/web/views/authentication.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/authentication.py	Fri Apr 05 17:58:19 2019 +0200
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """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 '<session %s (0x%x)>' % (text_type(self.user.login), id(self))
+    def __str__(self):
+        return '<session %s (0x%x)>' % (self.user.login, id(self))
 
     @property
     def anonymous_session(self):
--- a/cubicweb/web/views/autoform.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/autoform.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/basecontrollers.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/basecontrollers.py	Fri Apr 05 17:58:19 2019 +0200
@@ -19,8 +19,7 @@
 object to handle publication.
 """
 
-from six import text_type
-from six.moves import http_client
+import http.client
 
 from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError,
                       AuthenticationError, UndoTransactionException,
@@ -44,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):
@@ -187,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)
 
 
--- a/cubicweb/web/views/baseviews.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/baseviews.py	Fri Apr 05 17:58:19 2019 +0200
@@ -77,8 +77,6 @@
 
 from cubicweb import _
 
-from six.moves import range
-
 from logilab.mtconverter import TransformError, xml_escape
 from logilab.common.registry import yes
 
--- a/cubicweb/web/views/boxes.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/boxes.py	Fri Apr 05 17:58:19 2019 +0200
@@ -28,8 +28,6 @@
 
 from cubicweb import _
 
-from six import text_type, add_metaclass
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized
@@ -212,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'])
--- a/cubicweb/web/views/csvexport.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/csvexport.py	Fri Apr 05 17:58:19 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': ';',
--- a/cubicweb/web/views/cwsources.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/cwsources.py	Fri Apr 05 17:58:19 2019 +0200
@@ -21,8 +21,6 @@
 
 import logging
 
-from six.moves import range
-
 from logilab.common.decorators import cachedproperty
 
 from cubicweb import _
--- a/cubicweb/web/views/cwuser.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/cwuser.py	Fri Apr 05 17:58:19 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()),
     }
--- a/cubicweb/web/views/debug.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/debug.py	Fri Apr 05 17:58:19 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())]
--- a/cubicweb/web/views/editcontroller.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/editcontroller.py	Fri Apr 05 17:58:19 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)
--- a/cubicweb/web/views/editforms.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/editforms.py	Fri Apr 05 17:58:19 2019 +0200
@@ -23,8 +23,6 @@
 
 from copy import copy
 
-from six.moves import range
-
 from logilab.common.registry import yes
 
 from cubicweb import _
--- a/cubicweb/web/views/formrenderers.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/formrenderers.py	Fri Apr 05 17:58:19 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:
--- a/cubicweb/web/views/forms.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/forms.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/web/views/ibreadcrumbs.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/ibreadcrumbs.py	Fri Apr 05 17:58:19 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):
--- a/cubicweb/web/views/idownloadable.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/idownloadable.py	Fri Apr 05 17:58:19 2019 +0200
@@ -22,8 +22,6 @@
 
 from cubicweb import _
 
-from six.moves import range
-
 from logilab.mtconverter import BINARY_ENCODINGS, TransformError, xml_escape
 
 from cubicweb import tags
--- a/cubicweb/web/views/magicsearch.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/magicsearch.py	Fri Apr 05 17:58:19 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
@@ -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()]
--- a/cubicweb/web/views/navigation.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/navigation.py	Fri Apr 05 17:58:19 2019 +0200
@@ -50,8 +50,6 @@
 
 from datetime import datetime
 
-from six import text_type
-
 from rql.nodes import VariableRef, Constant
 
 from logilab.mtconverter import xml_escape
@@ -193,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')
--- a/cubicweb/web/views/owl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/owl.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/pyviews.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/pyviews.py	Fri Apr 05 17:58:19 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):
--- a/cubicweb/web/views/rdf.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/rdf.py	Fri Apr 05 17:58:19 2019 +0200
@@ -20,8 +20,6 @@
 
 from cubicweb import _
 
-from six.moves import range
-
 from yams import xy
 
 from cubicweb.schema import VIRTUAL_RTYPES
--- a/cubicweb/web/views/schema.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/schema.py	Fri Apr 05 17:58:19 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'<br/>'.join(constraints))
 
 class CWAttributeOptionsCell(EntityView):
--- a/cubicweb/web/views/sparql.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/sparql.py	Fri Apr 05 17:58:19 2019 +0200
@@ -20,8 +20,6 @@
 
 from cubicweb import _
 
-from six.moves import range
-
 from yams import xy
 from rql import TypeResolverException
 
--- a/cubicweb/web/views/tableview.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/tableview.py	Fri Apr 05 17:58:19 2019 +0200
@@ -66,9 +66,6 @@
 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
@@ -286,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)
@@ -717,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:
@@ -912,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.
 
@@ -1178,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()
--- a/cubicweb/web/views/tabs.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/tabs.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/timetable.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/timetable.py	Fri Apr 05 17:58:19 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
 
--- a/cubicweb/web/views/uicfg.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/uicfg.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/urlrewrite.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/urlrewrite.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/wdoc.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/wdoc.py	Fri Apr 05 17:58:19 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
 
 
--- a/cubicweb/web/views/workflow.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/workflow.py	Fri Apr 05 17:58:19 2019 +0200
@@ -24,8 +24,6 @@
 
 from cubicweb import _
 
-from six import text_type
-
 from logilab.mtconverter import xml_escape
 
 from cubicweb import Unauthorized
@@ -309,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}))
 
--- a/cubicweb/web/views/xbel.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/xbel.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/web/views/xmlrss.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/views/xmlrss.py	Fri Apr 05 17:58:19 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,
--- a/cubicweb/web/webconfig.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/webconfig.py	Fri Apr 05 17:58:19 2019 +0200
@@ -25,8 +25,6 @@
 from uuid import uuid4
 from os.path import dirname, join, exists, split, isdir
 
-from six import text_type
-
 from logilab.common.decorators import cached, cachedproperty
 from logilab.common.configuration import Method, merge_options
 
@@ -134,8 +132,6 @@
         try:
             user = self['anonymous-user'] or None
             passwd = self['anonymous-password']
-            if user:
-                user = text_type(user)
         except KeyError:
             user, passwd = None, None
         except UnicodeDecodeError:
@@ -301,7 +297,7 @@
     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
--- a/cubicweb/web/webctl.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/web/webctl.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/wfutils.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/wfutils.py	Fri Apr 05 17:58:19 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:
--- a/cubicweb/wsgi/__init__.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/wsgi/__init__.py	Fri Apr 05 17:58:19 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"""
--- a/cubicweb/wsgi/handler.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/wsgi/handler.py	Fri Apr 05 17:58:19 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
--- a/cubicweb/wsgi/request.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/cubicweb/wsgi/request.py	Fri Apr 05 17:58:19 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)
--- a/debian/control	Fri Apr 05 17:21:14 2019 +0200
+++ b/debian/control	Fri Apr 05 17:58:19 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,
@@ -46,7 +45,6 @@
  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,
--- a/doc/changes/3.27.rst	Fri Apr 05 17:21:14 2019 +0200
+++ b/doc/changes/3.27.rst	Fri Apr 05 17:58:19 2019 +0200
@@ -25,6 +25,8 @@
 * 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.
+
 Deprecated code drops
 ---------------------
 
--- a/doc/tools/mode_plan.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/doc/tools/mode_plan.py	Fri Apr 05 17:58:19 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)
--- a/setup.py	Fri Apr 05 17:21:14 2019 +0200
+++ b/setup.py	Fri Apr 05 17:58:19 2019 +0200
@@ -63,7 +63,6 @@
     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',
@@ -73,7 +72,6 @@
         'passlib',
         'pytz',
         'Markdown',
-        'unittest2 >= 0.7.0',
         'filelock',
     ],
     entry_points={
--- a/tox.ini	Fri Apr 05 17:21:14 2019 +0200
+++ b/tox.ini	Fri Apr 05 17:58:19 2019 +0200
@@ -1,12 +1,12 @@
 [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
@@ -17,7 +17,6 @@
   web: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/web/test
 
 [testenv:flake8]
-basepython=python3
 skip_install = true
 deps =
   flake8 >= 3.6