backport 3.22 changes
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 16 Jun 2016 15:53:01 +0200
changeset 11279 e4f11ef1face
parent 11277 baed516c6f6e (current diff)
parent 11278 19fcce6dc6d1 (diff)
child 11280 eb1d74ce6b61
backport 3.22 changes
cubicweb.spec
cubicweb/__init__.py
cubicweb/__pkginfo__.py
cubicweb/cwctl.py
cubicweb/dataimport/importer.py
cubicweb/dataimport/test/unittest_importer.py
cubicweb/devtools/test/requirements.txt
cubicweb/entity.py
cubicweb/etwist/test/requirements.txt
cubicweb/misc/migration/3.10.0_Any.py
cubicweb/misc/migration/3.10.9_Any.py
cubicweb/server/sources/ldapfeed.py
cubicweb/server/sources/native.py
cubicweb/server/test/unittest_ldapsource.py
cubicweb/test/data/schema.py
cubicweb/test/unittest_binary.py
cubicweb/test/unittest_entity.py
cubicweb/test/unittest_schema.py
cubicweb/web/request.py
cubicweb/web/test/requirements.txt
cubicweb/web/test/unittest_http.py
debian/control
--- a/.hgtags	Fri Jun 10 16:45:20 2016 +0200
+++ b/.hgtags	Thu Jun 16 15:53:01 2016 +0200
@@ -517,6 +517,9 @@
 8c5dabbcd4d9505c3a617f9dbe2b10172bdc2b3a 3.20.13
 8c5dabbcd4d9505c3a617f9dbe2b10172bdc2b3a debian/3.20.13-1
 8c5dabbcd4d9505c3a617f9dbe2b10172bdc2b3a centos/3.20.13-1
+f66a4895759e0913b1203943fc2cd7be1a821e05 3.20.14
+f66a4895759e0913b1203943fc2cd7be1a821e05 debian/3.20.14-1
+f66a4895759e0913b1203943fc2cd7be1a821e05 centos/3.20.14-1
 887c6eef807781560adcd4ecd2dea9011f5a6681 3.21.0
 887c6eef807781560adcd4ecd2dea9011f5a6681 debian/3.21.0-1
 887c6eef807781560adcd4ecd2dea9011f5a6681 centos/3.21.0-1
--- a/cubicweb/__init__.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/__init__.py	Thu Jun 16 15:53:01 2016 +0200
@@ -25,15 +25,21 @@
 import sys
 import warnings
 import zlib
-if (2, 7) <= sys.version_info < (2, 7, 4):
+
+warnings.filterwarnings('ignore', category=UserWarning,
+                        message='.*was already imported',
+                        module='.*pygments')
+
+
+from six import PY2, binary_type, text_type
+from six.moves import builtins
+
+if PY2:
     # http://bugs.python.org/issue10211
     from StringIO import StringIO as BytesIO
 else:
     from io import BytesIO
 
-from six import PY2, binary_type, text_type
-from six.moves import builtins, cPickle as pickle
-
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 from yams.constraints import BASE_CONVERTERS, BASE_CHECKERS
--- a/cubicweb/__pkginfo__.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/__pkginfo__.py	Thu Jun 16 15:53:01 2016 +0200
@@ -23,7 +23,7 @@
 modname = distname = "cubicweb"
 
 numversion = (3, 22, 2)
-version = '.'.join(str(num) for num in numversion)
+version = '.'.join(str(num) for num in numversion) + '.dev0'
 
 description = "a repository of entities / relations for knowledge management"
 author = "Logilab"
--- a/cubicweb/cwctl.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/cwctl.py	Thu Jun 16 15:53:01 2016 +0200
@@ -983,8 +983,12 @@
 # WSGI #########
 
 WSGI_CHOICES = {}
-from cubicweb.wsgi import server as stdlib_server
-WSGI_CHOICES['stdlib'] = stdlib_server
+try:
+    from cubicweb.wsgi import server as stdlib_server
+except ImportError:
+    pass
+else:
+    WSGI_CHOICES['stdlib'] = stdlib_server
 try:
     from cubicweb.wsgi import wz
 except ImportError:
@@ -1002,51 +1006,51 @@
 def wsgichoices():
     return tuple(WSGI_CHOICES)
 
-
-class WSGIStartHandler(InstanceCommand):
-    """Start an interactive wsgi server """
-    name = 'wsgi'
-    actionverb = 'started'
-    arguments = '<instance>'
+if WSGI_CHOICES:
+    class WSGIStartHandler(InstanceCommand):
+        """Start an interactive wsgi server """
+        name = 'wsgi'
+        actionverb = 'started'
+        arguments = '<instance>'
 
-    @property
-    def options(self):
-        return (
-        ("debug",
-         {'short': 'D', 'action': 'store_true',
-          'default': False,
-          'help': 'start server in debug mode.'}),
-        ('method',
-         {'short': 'm',
-          'type': 'choice',
-          'metavar': '<method>',
-          'default': 'stdlib',
-          'choices': wsgichoices(),
-          'help': 'wsgi utility/method'}),
-        ('loglevel',
-         {'short': 'l',
-          'type': 'choice',
-          'metavar': '<log level>',
-          'default': None,
-          'choices': ('debug', 'info', 'warning', 'error'),
-          'help': 'debug if -D is set, error otherwise',
-          }),
-        )
+        @property
+        def options(self):
+            return (
+                ("debug",
+                 {'short': 'D', 'action': 'store_true',
+                  'default': False,
+                  'help': 'start server in debug mode.'}),
+                ('method',
+                 {'short': 'm',
+                  'type': 'choice',
+                  'metavar': '<method>',
+                  'default': 'stdlib',
+                  'choices': wsgichoices(),
+                  'help': 'wsgi utility/method'}),
+                ('loglevel',
+                 {'short': 'l',
+                  'type': 'choice',
+                  'metavar': '<log level>',
+                  'default': None,
+                  'choices': ('debug', 'info', 'warning', 'error'),
+                  'help': 'debug if -D is set, error otherwise',
+              }),
+            )
 
-    def wsgi_instance(self, appid):
-        config = cwcfg.config_for(appid, debugmode=self['debug'])
-        init_cmdline_log_threshold(config, self['loglevel'])
-        assert config.name == 'all-in-one'
-        meth = self['method']
-        server = WSGI_CHOICES[meth]
-        return server.run(config)
+        def wsgi_instance(self, appid):
+            config = cwcfg.config_for(appid, debugmode=self['debug'])
+            init_cmdline_log_threshold(config, self['loglevel'])
+            assert config.name == 'all-in-one'
+            meth = self['method']
+            server = WSGI_CHOICES[meth]
+            return server.run(config)
 
+    CWCTL.register(WSGIStartHandler)
 
 
 for cmdcls in (ListCommand,
                CreateInstanceCommand, DeleteInstanceCommand,
                StartInstanceCommand, StopInstanceCommand, RestartInstanceCommand,
-               WSGIStartHandler,
                ReloadConfigurationCommand, StatusCommand,
                UpgradeInstanceCommand,
                ListVersionsInstanceCommand,
--- a/cubicweb/dataimport/importer.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/dataimport/importer.py	Thu Jun 16 15:53:01 2016 +0200
@@ -319,6 +319,7 @@
         """
         schema = self.schema
         extid2eid = self.extid2eid
+        order_hint = list(self.etypes_order_hint)
         for ext_entity in ext_entities:
             # check data in the transitional representation and prepare it for
             # later insertion in the database
@@ -328,12 +329,17 @@
                 queue.setdefault(ext_entity.etype, []).append(ext_entity)
                 continue
             yield ext_entity
+            if not queue:
+                continue
             # check for some entities in the queue that may now be ready. We'll have to restart
             # search for ready entities until no one is generated
+            for etype in queue:
+                if etype not in order_hint:
+                    order_hint.append(etype)
             new = True
             while new:
                 new = False
-                for etype in self.etypes_order_hint:
+                for etype in order_hint:
                     if etype in queue:
                         new_queue = []
                         for ext_entity in queue[etype]:
@@ -377,8 +383,8 @@
                 try:
                     subject_eid = extid2eid[subject_uri]
                     object_eid = extid2eid[object_uri]
-                except KeyError:
-                    missing_relations.append((subject_uri, rtype, object_uri))
+                except KeyError as exc:
+                    missing_relations.append((subject_uri, rtype, object_uri, exc))
                     continue
                 if (subject_eid, object_eid) not in existing:
                     prepare_insert_relation(subject_eid, rtype, object_eid)
@@ -400,8 +406,9 @@
                 raise Exception('\n'.join(msgs))
         if missing_relations:
             msgs = ["can't create some relations, is there missing data?"]
-            for subject_uri, rtype, object_uri in missing_relations:
-                msgs.append("%s %s %s" % (subject_uri, rtype, object_uri))
+            for subject_uri, rtype, object_uri, exc in missing_relations:
+                msgs.append("Could not find %s when trying to insert (%s, %s, %s)"
+                            % (exc, subject_uri, rtype, object_uri))
             map(error, msgs)
             if self.raise_on_error:
                 raise Exception('\n'.join(msgs))
--- a/cubicweb/dataimport/test/unittest_importer.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/dataimport/test/unittest_importer.py	Thu Jun 16 15:53:01 2016 +0200
@@ -124,6 +124,19 @@
             self.assertEqual(entity.nom, u'Richelieu')
             self.assertEqual(len(entity.connait), 0)
 
+    def test_import_order(self):
+        """Check import of ext entity in both order"""
+        with self.admin_access.repo_cnx() as cnx:
+            importer = self.importer(cnx)
+            richelieu = ExtEntity('Personne', 3, {'nom': set([u'Richelieu']),
+                                                  'enfant': set([4])})
+            athos = ExtEntity('Personne', 4, {'nom': set([u'Athos'])})
+            importer.import_entities([richelieu, athos])
+            cnx.commit()
+            rset = cnx.execute('Any X WHERE X is Personne, X nom "Richelieu"')
+            entity = rset.get_entity(0, 0)
+            self.assertEqual(entity.enfant[0].nom, 'Athos')
+
     def test_update(self):
         """Check update of ext entity"""
         with self.admin_access.repo_cnx() as cnx:
--- a/cubicweb/entity.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/entity.py	Thu Jun 16 15:53:01 2016 +0200
@@ -783,7 +783,7 @@
         for rschema in self.e_schema.subject_relations():
             if rschema.type in skip_copy_for['subject']:
                 continue
-            if rschema.final or rschema.meta:
+            if rschema.final or rschema.meta or rschema.rule:
                 continue
             # skip already defined relations
             if getattr(self, rschema.type):
@@ -802,7 +802,7 @@
             execute(rql, {'x': self.eid, 'y': ceid})
             self.cw_clear_relation_cache(rschema.type, 'subject')
         for rschema in self.e_schema.object_relations():
-            if rschema.meta:
+            if rschema.meta or rschema.rule:
                 continue
             # skip already defined relations
             if self.related(rschema.type, 'object'):
--- a/cubicweb/misc/migration/3.10.0_Any.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/misc/migration/3.10.0_Any.py	Thu Jun 16 15:53:01 2016 +0200
@@ -1,7 +1,5 @@
 from six import text_type
 
-from cubicweb.server.session import hooks_control
-
 for uri, cfg in config.read_sources_file().items():
     if uri in ('system', 'admin'):
         continue
@@ -11,7 +9,7 @@
 add_relation_definition('CWSource', 'cw_source', 'CWSource')
 add_entity_type('CWSourceHostConfig')
 
-with hooks_control(session, session.HOOKS_ALLOW_ALL, 'cw.sources'):
+with session.allow_all_hooks_but('cw.sources'):
     create_entity('CWSource', type=u'native', name=u'system')
 commit()
 
--- a/cubicweb/misc/migration/3.10.9_Any.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/misc/migration/3.10.9_Any.py	Thu Jun 16 15:53:01 2016 +0200
@@ -14,7 +14,7 @@
     enabled = interactive_mode
     with progress(title=title, nbops=nbops, size=30, enabled=enabled) as pb:
         for i,  row in enumerate(rset):
-            with hooks_control(session, session.HOOKS_DENY_ALL, 'integrity'):
+            with session.deny_all_hooks_but('integrity'):
                 data = {'eid': row[0], 'cwuri': row[1].replace(u'/eid', u'')}
                 rql('SET X cwuri %(cwuri)s WHERE X eid %(eid)s', data)
             if not i % 100: # commit every 100 entities to limit memory consumption
--- a/cubicweb/server/sources/ldapfeed.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/server/sources/ldapfeed.py	Thu Jun 16 15:53:01 2016 +0200
@@ -284,26 +284,27 @@
         else:
             # user specified, we want to check user/password, no need to return
             # the connection which will be thrown out
-            self._authenticate(conn, user, userpwd)
+            if not self._authenticate(conn, user, userpwd):
+                raise AuthenticationError()
         return conn
 
     def _auth_simple(self, conn, user, userpwd):
         conn.authentication = ldap3.AUTH_SIMPLE
         conn.user = user['dn']
         conn.password = userpwd
-        conn.bind()
+        return conn.bind()
 
     def _auth_digest_md5(self, conn, user, userpwd):
         conn.authentication = ldap3.AUTH_SASL
         conn.sasl_mechanism = 'DIGEST-MD5'
         # realm, user, password, authz-id
         conn.sasl_credentials = (None, user['dn'], userpwd, None)
-        conn.bind()
+        return conn.bind()
 
     def _auth_gssapi(self, conn, user, userpwd):
         conn.authentication = ldap3.AUTH_SASL
         conn.sasl_mechanism = 'GSSAPI'
-        conn.bind()
+        return conn.bind()
 
     def _search(self, cnx, base, scope,
                 searchstr='(objectClass=*)', attrs=()):
--- a/cubicweb/server/test/unittest_ldapsource.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/server/test/unittest_ldapsource.py	Thu Jun 16 15:53:01 2016 +0200
@@ -246,6 +246,8 @@
             # ensure we won't be logged against
             self.assertRaises(AuthenticationError,
                               source.authenticate, cnx, 'toto', 'toto')
+            self.assertRaises(AuthenticationError,
+                              source.authenticate, cnx, 'syt', 'toto')
             self.assertTrue(source.authenticate(cnx, 'syt', 'syt'))
         session = self.repo.new_session('syt', password='syt')
         self.assertTrue(session)
--- a/cubicweb/test/data/schema.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/test/data/schema.py	Thu Jun 16 15:53:01 2016 +0200
@@ -17,7 +17,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
 from yams.buildobjs import (EntityType, String, RichString, Bytes,
-                            SubjectRelation, RelationDefinition)
+                            ComputedRelation, SubjectRelation, RelationDefinition)
 
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLVocabularyConstraint)
@@ -26,6 +26,10 @@
 from cubicweb import _
 
 
+class buddies(ComputedRelation):
+    rule = 'S in_group G, O in_group G'
+
+
 class Personne(EntityType):
     nom = String(required=True)
     prenom = String()
--- a/cubicweb/test/unittest_binary.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/test/unittest_binary.py	Thu Jun 16 15:53:01 2016 +0200
@@ -1,10 +1,29 @@
-from six import PY2
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
 from unittest import TestCase
-from tempfile import NamedTemporaryFile
 import os.path as osp
+import pickle
+
+from six import PY2
 
 from logilab.common.shellutils import tempdir
+
 from cubicweb import Binary
 
 
@@ -48,6 +67,11 @@
             bobj = Binary.from_file(fpath)
             self.assertEqual(bobj.getvalue(), b'binaryblob')
 
+    def test_pickleable(self):
+        b = Binary(b'toto')
+        bb = pickle.loads(pickle.dumps(b))
+        self.assertEqual(b, bb)
+
 
 if __name__ == '__main__':
     from unittest import main
--- a/cubicweb/test/unittest_entity.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/test/unittest_entity.py	Thu Jun 16 15:53:01 2016 +0200
@@ -138,6 +138,22 @@
             e.cw_clear_relation_cache('in_state', 'subject')
             self.assertEqual(e.cw_adapt_to('IWorkflowable').state, 'activated')
 
+    def test_copy_exclude_computed_relations(self):
+        """The `CWUser buddies CWUser` (computed) relation should not be copied.
+        """
+        with self.admin_access.cnx() as cnx:
+            friends = cnx.create_entity('CWGroup', name=u'friends')
+            bob = self.create_user(cnx, u'bob', groups=('friends',))
+            cnx.create_entity('EmailAddress', address=u'bob@cubicweb.org',
+                              reverse_use_email=bob)
+            alice = self.create_user(cnx, u'alices', groups=('friends',))
+            cnx.commit()
+            charles = self.create_user(cnx, u'charles')
+            cnx.commit()
+            # Just ensure this does not crash (it would if computed relation
+            # attempted to be copied).
+            charles.copy_relations(bob.eid)
+
     def test_related_cache_both(self):
         with self.admin_access.web_request() as req:
             user = req.execute('Any X WHERE X eid %(x)s', {'x':req.user.eid}).get_entity(0, 0)
--- a/cubicweb/test/unittest_schema.py	Fri Jun 10 16:45:20 2016 +0200
+++ b/cubicweb/test/unittest_schema.py	Thu Jun 16 15:53:01 2016 +0200
@@ -178,7 +178,7 @@
         self.assertListEqual(sorted(expected_entities), entities)
         relations = sorted([str(r) for r in schema.relations()])
         expected_relations = ['actionnaire', 'add_permission', 'address', 'alias', 'allowed_transition', 'associe',
-                              'bookmarked_by', 'by_transition',
+                              'bookmarked_by', 'by_transition', 'buddies',
 
                               'cardinality', 'comment', 'comment_format',
                               'composite', 'condition', 'config', 'connait',
@@ -225,7 +225,7 @@
 
         eschema = schema.eschema('CWUser')
         rels = sorted(str(r) for r in eschema.subject_relations())
-        self.assertListEqual(rels, ['created_by', 'creation_date', 'custom_workflow',
+        self.assertListEqual(rels, ['buddies', 'created_by', 'creation_date', 'custom_workflow',
                                     'cw_source', 'cwuri', 'eid',
                                     'evaluee', 'firstname', 'has_group_permission',
                                     'has_text', 'identity',
@@ -235,7 +235,7 @@
                                     'primary_email', 'surname', 'upassword',
                                     'use_email'])
         rels = sorted(r.type for r in eschema.object_relations())
-        self.assertListEqual(rels, ['bookmarked_by', 'created_by', 'for_user',
+        self.assertListEqual(rels, ['bookmarked_by', 'buddies', 'created_by', 'for_user',
                                      'identity', 'owned_by', 'wf_info_for'])
         rschema = schema.rschema('relation_type')
         properties = rschema.rdef('CWAttribute', 'CWRType')
--- a/debian/changelog	Fri Jun 10 16:45:20 2016 +0200
+++ b/debian/changelog	Thu Jun 16 15:53:01 2016 +0200
@@ -64,6 +64,12 @@
 
  -- Julien Cristau <julien.cristau@logilab.fr>  Fri, 10 Jul 2015 17:04:11 +0200
 
+cubicweb (3.20.14-1) unstable; urgency=medium
+
+  * new upstream release
+
+ -- Julien Cristau <julien.cristau@logilab.fr>  Mon, 21 Mar 2016 17:59:22 +0100
+
 cubicweb (3.20.13-1) unstable; urgency=medium
 
   * new upstream release