# HG changeset patch # User Aurelien Campeas # Date 1381404371 -7200 # Node ID 5f2c5eb1a820172ef222c88406c72585dfb6c186 # Parent 614762cdc35719f82756f77827eb7b229fa151e6# Parent 1709dd30387c70e29dcfd3ddcee991a3517d58c7 [merge] backport stable fixes diff -r 614762cdc357 -r 5f2c5eb1a820 .hgtags --- a/.hgtags Wed Oct 09 11:13:56 2013 +0200 +++ b/.hgtags Thu Oct 10 13:26:11 2013 +0200 @@ -311,3 +311,5 @@ 909eb8b584c437b3d2580beff1325c3d5b5dcfb5 cubicweb-centos-version-3.17.8-1 909eb8b584c437b3d2580beff1325c3d5b5dcfb5 cubicweb-version-3.17.8 909eb8b584c437b3d2580beff1325c3d5b5dcfb5 cubicweb-debian-version-3.17.8-1 +5668d210e49c910180ff27712b6ae9ce8286e06c cubicweb-version-3.17.9 +5668d210e49c910180ff27712b6ae9ce8286e06c cubicweb-debian-version-3.17.9-1 diff -r 614762cdc357 -r 5f2c5eb1a820 __pkginfo__.py --- a/__pkginfo__.py Wed Oct 09 11:13:56 2013 +0200 +++ b/__pkginfo__.py Thu Oct 10 13:26:11 2013 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 17, 8) +numversion = (3, 17, 9) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" diff -r 614762cdc357 -r 5f2c5eb1a820 debian/changelog --- a/debian/changelog Wed Oct 09 11:13:56 2013 +0200 +++ b/debian/changelog Thu Oct 10 13:26:11 2013 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.17.9-1) unstable; urgency=low + + * new upstream release + + -- Julien Cristau Tue, 08 Oct 2013 17:57:04 +0200 + cubicweb (3.17.8-1) unstable; urgency=low * new upstream release diff -r 614762cdc357 -r 5f2c5eb1a820 debian/control --- a/debian/control Wed Oct 09 11:13:56 2013 +0200 +++ b/debian/control Thu Oct 10 13:26:11 2013 +0200 @@ -12,7 +12,7 @@ python (>= 2.6), python-sphinx, python-logilab-common, - python-unittest2, + python-unittest2 | python (>= 2.7), python-logilab-mtconverter, python-rql, python-yams (>= 0.37), diff -r 614762cdc357 -r 5f2c5eb1a820 doc/book/en/admin/setup.rst --- a/doc/book/en/admin/setup.rst Wed Oct 09 11:13:56 2013 +0200 +++ b/doc/book/en/admin/setup.rst Thu Oct 10 13:26:11 2013 +0200 @@ -131,25 +131,40 @@ `pip` install ------------- -pip_ is a python utility that helps downloading, building, installing, and -managing python packages and their dependencies. It is fully compatible with -`virtualenv`_ and installs the packages from sources published on the -`The Python Package Index`_. +`pip `_ is a python tool that helps downloading, +building, installing, and managing Python packages and their dependencies. It +is fully compatible with `virtualenv`_ and installs the packages from sources +published on the `The Python Package Index`_. -.. _`pip`: http://pip.openplans.org/ .. _`virtualenv`: http://virtualenv.openplans.org/ A working compilation chain is needed to build the modules that include C -extensions. If you definitively wont, installing `Lxml `_, +extensions. If you really do not want to compile anything, installing `Lxml `_, `Twisted Web `_ and `libgecode `_ will help. -To install |cubicweb| and its dependencies, just run:: +For Debian, these minimal dependencies can be obtained by doing:: + + apt-get install gcc python-pip python-dev python-lxml + +or, if you prefer to get as much as possible from pip:: + + apt-get install gcc python-pip python-dev libxslt1-dev libxml2-dev + +For Windows, you can install pre-built packages (possible `source +`_). For a minimal setup, install +`pip `_, `setuptools +`_, `libxml-python +`_, `lxml +`_ and `twisted +`_ from this source making +sure to choose the correct architecture and version of Python. + +Finally, install |cubicweb| and its dependencies, by running:: pip install cubicweb -There is also a wide variety of :ref:`cubes `. You can access a -list of available cubes on +Many other :ref:`cubes ` are available. A list is available at `PyPI `_ or at the `CubicWeb.org forge`_. diff -r 614762cdc357 -r 5f2c5eb1a820 entity.py --- a/entity.py Wed Oct 09 11:13:56 2013 +0200 +++ b/entity.py Thu Oct 10 13:26:11 2013 +0200 @@ -1307,7 +1307,8 @@ @deprecated('[3.16] use cw_set() instead of set_attributes()') def set_attributes(self, **kwargs): # XXX cw_set_attributes - self.cw_set(**kwargs) + if kwargs: + self.cw_set(**kwargs) @deprecated('[3.16] use cw_set() instead of set_relations()') def set_relations(self, **kwargs): # XXX cw_set_relations @@ -1318,7 +1319,8 @@ (meaning that all relations of the given type from or to this object should be deleted). """ - self.cw_set(**kwargs) + if kwargs: + self.cw_set(**kwargs) @deprecated('[3.13] use entity.cw_clear_all_caches()') def clear_all_caches(self): diff -r 614762cdc357 -r 5f2c5eb1a820 hooks/__init__.py --- a/hooks/__init__.py Wed Oct 09 11:13:56 2013 +0200 +++ b/hooks/__init__.py Thu Oct 10 13:26:11 2013 +0200 @@ -59,7 +59,9 @@ def update_feeds(repo): # don't iter on repo.sources which doesn't include copy based # sources (the one we're looking for) - for source in repo.sources_by_eid.itervalues(): + # take a list to avoid iterating on a dictionary which size may + # change + for source in list(repo.sources_by_eid.values()): if (not source.copy_based_source or not repo.config.source_enabled(source) or not source.config['synchronize']): diff -r 614762cdc357 -r 5f2c5eb1a820 hooks/notification.py --- a/hooks/notification.py Wed Oct 09 11:13:56 2013 +0200 +++ b/hooks/notification.py Thu Oct 10 13:26:11 2013 +0200 @@ -52,7 +52,7 @@ All others Operations end up adding data to this Operation. The notification are done on ``postcommit_event`` to make sure to prevent - sending notification about rollbacked data. + sending notification about rolled back data. """ containercls = list diff -r 614762cdc357 -r 5f2c5eb1a820 hooks/test/unittest_integrity.py --- a/hooks/test/unittest_integrity.py Wed Oct 09 11:13:56 2013 +0200 +++ b/hooks/test/unittest_integrity.py Thu Oct 10 13:26:11 2013 +0200 @@ -41,9 +41,6 @@ self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"') self.commit() - def test_delete_required_relations_object(self): - self.skipTest('no sample in the schema ! YAGNI ? Kermaat ?') - def test_static_vocabulary_check(self): self.assertRaises(ValidationError, self.execute, diff -r 614762cdc357 -r 5f2c5eb1a820 misc/scripts/ldapuser2ldapfeed.py --- a/misc/scripts/ldapuser2ldapfeed.py Wed Oct 09 11:13:56 2013 +0200 +++ b/misc/scripts/ldapuser2ldapfeed.py Thu Oct 10 13:26:11 2013 +0200 @@ -95,5 +95,5 @@ commit() else: rollback() - print 'rollbacked' + print 'rolled back' diff -r 614762cdc357 -r 5f2c5eb1a820 rset.py --- a/rset.py Wed Oct 09 11:13:56 2013 +0200 +++ b/rset.py Thu Oct 10 13:26:11 2013 +0200 @@ -45,7 +45,7 @@ :param rql: the original RQL query string """ - def __init__(self, results, rql, args=None, description=(), rqlst=None): + def __init__(self, results, rql, args=None, description=None, rqlst=None): self.rows = results self.rowcount = results and len(results) or 0 # original query and arguments @@ -53,7 +53,7 @@ self.args = args # entity types for each cell (same shape as rows) # maybe discarded if specified when the query has been executed - self.description = description + self.description = description or [] # parsed syntax tree if rqlst is not None: rqlst.schema = None # reset schema in case of pyro transfert diff -r 614762cdc357 -r 5f2c5eb1a820 schema.py --- a/schema.py Wed Oct 09 11:13:56 2013 +0200 +++ b/schema.py Thu Oct 10 13:26:11 2013 +0200 @@ -25,7 +25,7 @@ from logging import getLogger from warnings import warn -from logilab.common.decorators import cached, clear_cache, monkeypatch +from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty from logilab.common.logging_ext import set_log_methods from logilab.common.deprecation import deprecated, class_moved, moved from logilab.common.textutils import splitstrip @@ -696,7 +696,7 @@ # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None # to be defined in concrete classes - full_rql = None + rqlst = None predefined_variables = None def __init__(self, expression, mainvars, eid): @@ -715,7 +715,7 @@ self.mainvars = mainvars self.expression = normalize_expression(expression) try: - self.rqlst = parse(self.full_rql, print_errors=False).children[0] + self.full_rql = self.rqlst.as_string() except RQLSyntaxError: raise RQLSyntaxError(expression) for mainvar in mainvars: @@ -730,6 +730,8 @@ 'expression %s', mainvar, self) # syntax tree used by read security (inserted in queries when necessary) self.snippet_rqlst = parse(self.minimal_rql, print_errors=False).children[0] + # graph of links between variables, used by rql rewriter + self.vargraph = vargraph(self.rqlst) def __str__(self): return self.full_rql @@ -756,6 +758,15 @@ def __setstate__(self, state): self.__init__(*state) + @cachedproperty + def rqlst(self): + select = parse(self.minimal_rql, print_errors=False).children[0] + defined = set(split_expression(self.expression)) + for varname in self.predefined_variables: + if varname in defined: + select.add_eid_restriction(select.get_variable(varname), varname.lower(), 'Substitute') + return select + # permission rql expression specific stuff ################################# @cached @@ -874,25 +885,11 @@ # rql expressions for use in permission definition ############################# class ERQLExpression(RQLExpression): - predefined_variables = 'UX' + predefined_variables = 'XU' def __init__(self, expression, mainvars=None, eid=None): RQLExpression.__init__(self, expression, mainvars or 'X', eid) - @property - def full_rql(self): - rql = self.minimal_rql - rqlst = getattr(self, 'rqlst', None) # may be not set yet - if rqlst is not None: - defined = rqlst.defined_vars - else: - defined = set(split_expression(self.expression)) - if 'X' in defined: - rql += ', X eid %(x)s' - if 'U' in defined: - rql += ', U eid %(u)s' - return rql - def check(self, _cw, eid=None, creating=False, **kwargs): if 'X' in self.rqlst.defined_vars: if eid is None: @@ -931,30 +928,12 @@ class RRQLExpression(RQLExpression): - predefined_variables = 'USO' + predefined_variables = 'SOU' def __init__(self, expression, mainvars=None, eid=None): if mainvars is None: mainvars = guess_rrqlexpr_mainvars(expression) RQLExpression.__init__(self, expression, mainvars, eid) - # graph of links between variable, used by rql rewriter - self.vargraph = vargraph(self.rqlst) - - @property - def full_rql(self): - rql = self.minimal_rql - rqlst = getattr(self, 'rqlst', None) # may be not set yet - if rqlst is not None: - defined = rqlst.defined_vars - else: - defined = set(split_expression(self.expression)) - if 'S' in defined: - rql += ', S eid %(s)s' - if 'O' in defined: - rql += ', O eid %(o)s' - if 'U' in defined: - rql += ', U eid %(u)s' - return rql def check(self, _cw, fromeid=None, toeid=None): kwargs = {} diff -r 614762cdc357 -r 5f2c5eb1a820 server/hook.py --- a/server/hook.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/hook.py Thu Oct 10 13:26:11 2013 +0200 @@ -41,7 +41,7 @@ defined over data events. Also, some :class:`~cubicweb.server.hook.Operation` may be registered by hooks, -which will be fired when the transaction is commited or rollbacked. +which will be fired when the transaction is commited or rolled back. The purpose of data event hooks is usually to complement the data model as defined in the schema, which is static by nature and only provide a restricted @@ -705,10 +705,10 @@ * `rollback`: - the transaction has been either rollbacked either: + the transaction has been either rolled back either: * intentionaly - * a 'precommit' event failed, in which case all operations are rollbacked + * a 'precommit' event failed, in which case all operations are rolled back once 'revertprecommit'' has been called * `postcommit`: @@ -770,7 +770,7 @@ """ def rollback_event(self): - """the observed connections set has been rollbacked + """the observed connections set has been rolled back do nothing by default """ @@ -1034,7 +1034,7 @@ type/source cache eids of entities added in that transaction. NOTE: querier's rqlst/solutions cache may have been polluted too with - queries such as Any X WHERE X eid 32 if 32 has been rollbacked however + queries such as Any X WHERE X eid 32 if 32 has been rolled back however generated queries are unpredictable and analysing all the cache probably too expensive. Notice that there is no pb when using args to specify eids instead of giving them into the rql string. @@ -1042,7 +1042,7 @@ data_key = 'neweids' def rollback_event(self): - """the observed connections set has been rollbacked, + """the observed connections set has been rolled back, remove inserted eid from repository type/source cache """ try: @@ -1056,7 +1056,7 @@ """ data_key = 'pendingeids' def postcommit_event(self): - """the observed connections set has been rollbacked, + """the observed connections set has been rolled back, remove inserted eid from repository type/source cache """ try: diff -r 614762cdc357 -r 5f2c5eb1a820 server/querier.py --- a/server/querier.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/querier.py Thu Oct 10 13:26:11 2013 +0200 @@ -632,7 +632,7 @@ results = plan.execute() except (Unauthorized, ValidationError): # getting an Unauthorized/ValidationError exception means the - # transaction must been rollbacked + # transaction must be rolled back # # notes: # * we should not reset the connections set here, since we don't want the diff -r 614762cdc357 -r 5f2c5eb1a820 server/repository.py --- a/server/repository.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/repository.py Thu Oct 10 13:26:11 2013 +0200 @@ -407,7 +407,7 @@ return self._cnxsets_pool.get(True, timeout=5) except Queue.Empty: raise Exception('no connections set available after 5 secs, probably either a ' - 'bug in code (too many uncommited/rollbacked ' + 'bug in code (too many uncommited/rolled back ' 'connections) or too much load on the server (in ' 'which case you can try to set a bigger ' 'connections pool size)') @@ -868,7 +868,7 @@ """close the session with the given id""" session = self._get_session(sessionid, setcnxset=True, txid=txid, checkshuttingdown=checkshuttingdown) - # operation uncommited before close are rollbacked before hook is called + # operation uncommited before close are rolled back before hook is called session.rollback(free_cnxset=False) self.hm.call_hooks('session_close', session) # commit session at this point in case write operation has been done @@ -1009,7 +1009,7 @@ def _get_session(self, sessionid, setcnxset=False, txid=None, checkshuttingdown=True): - """return the user associated to the given session identifier""" + """return the session associated with the given session identifier""" if checkshuttingdown and self.shutting_down: raise ShuttingDown('Repository is shutting down') try: diff -r 614762cdc357 -r 5f2c5eb1a820 server/session.py --- a/server/session.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/session.py Thu Oct 10 13:26:11 2013 +0200 @@ -67,7 +67,7 @@ class transaction(object): - """Ensure that the transaction is either commited or rollbacked at exit + """Ensure that the transaction is either commited or rolled back at exit Context manager to enter a transaction for a session: when exiting the `with` block on exception, call `session.rollback()`, else call @@ -201,18 +201,17 @@ class CnxSetTracker(object): """Keep track of which transaction use which cnxset. - There should be one of this object per session plus one another for - internal session. + There should be one of these object per session (including internal sessions). - Session object are responsible of creating their CnxSetTracker object. + Session objects are responsible of creating their CnxSetTracker object. - Transaction should use the :meth:`record` and :meth:`forget` to inform the - tracker of cnxset they have acquired. + Transactions should use the :meth:`record` and :meth:`forget` to inform the + tracker of cnxsets they have acquired. .. automethod:: cubicweb.server.session.CnxSetTracker.record .. automethod:: cubicweb.server.session.CnxSetTracker.forget - Session use the :meth:`close` and :meth:`wait` method when closing. + Sessions use the :meth:`close` and :meth:`wait` methods when closing. .. automethod:: cubicweb.server.session.CnxSetTracker.close .. automethod:: cubicweb.server.session.CnxSetTracker.wait @@ -233,12 +232,12 @@ return self._condition.__exit__(*args) def record(self, txid, cnxset): - """Inform the tracker that a txid have acquired a cnxset + """Inform the tracker that a txid has acquired a cnxset - This methode is to be used by Transaction object. + This method is to be used by Transaction objects. This method fails when: - - The txid already have a recorded cnxset. + - The txid already has a recorded cnxset. - The tracker is not active anymore. Notes about the caller: @@ -246,7 +245,7 @@ (2) It must be prepared to release the cnxset if the `cnxsettracker.forget` call fails. (3) It should acquire the tracker lock until the very end of the operation. - (4) However It take care to lock the CnxSetTracker object after having + (4) However it must only lock the CnxSetTracker object after having retrieved the cnxset to prevent deadlock. A typical usage look like:: @@ -261,13 +260,13 @@ repo._free_cnxset(cnxset) # (2) raise """ - # dubious since the caller is suppose to have acquired it anyway. + # dubious since the caller is supposed to have acquired it anyway. with self._condition: if not self._active: raise SessionClosedError('Closed') old = self._record.get(txid) if old is not None: - raise ValueError('"%s" already have a cnx_set (%r)' + raise ValueError('transaction "%s" already has a cnx_set (%r)' % (txid, old)) self._record[txid] = cnxset @@ -307,19 +306,19 @@ def close(self): """Marks the tracker as inactive. - This methode is to be used by Session object. + This method is to be used by Session objects. - Inactive tracker does not accept new record anymore. + An inactive tracker does not accept new records anymore. """ with self._condition: self._active = False def wait(self, timeout=10): - """Wait for all recorded cnxset to be released + """Wait for all recorded cnxsets to be released - This methode is to be used by Session object. + This method is to be used by Session objects. - returns a tuple of transaction id that remains open. + Returns a tuple of transaction ids that remain open. """ with self._condition: if self._active: @@ -336,15 +335,15 @@ Holds all transaction related data - Database connections resource: + Database connection resources: :attr:`running_dbapi_query`, boolean flag telling if the executing query is coming from a dbapi connection or is a query from within the repository :attr:`cnxset`, the connections set to use to execute queries on sources. If the transaction is read only, the connection set may be freed between - actual query. This allows multiple transaction with a reasonable low - connection set pool size. control mechanism is detailed below + actual queries. This allows multiple transactions with a reasonably low + connection set pool size. Control mechanism is detailed below. .. automethod:: cubicweb.server.session.Transaction.set_cnxset .. automethod:: cubicweb.server.session.Transaction.free_cnxset @@ -357,7 +356,7 @@ Internal transaction data: - :attr:`data`,is a dictionary containing some shared data + :attr:`data` is a dictionary containing some shared data cleared at the end of the transaction. Hooks and operations may put arbitrary data in there, and this may also be used as a communication channel between the client and the repository. @@ -369,7 +368,7 @@ of None (not yet committing), 'precommit' (calling precommit event on operations), 'postcommit' (calling postcommit event on operations), 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error - has been raised during the transaction and so it must be rollbacked). + has been raised during the transaction and so it must be rolled back). Hooks controls: @@ -439,7 +438,6 @@ def transaction_data(self): return self.data - def clear(self): """reset internal data""" self.data = {} @@ -448,6 +446,7 @@ #: (None, 'precommit', 'postcommit', 'uncommitable') self.commit_state = None self.pruned_hooks_cache = {} + # Connection Set Management ############################################### @property def cnxset(self): @@ -499,7 +498,7 @@ # Entity cache management ################################################# # - # The transaction entity cache as held in tx.data it is removed at end the + # The transaction entity cache as held in tx.data is removed at the # end of the transaction (commit and rollback) # # XXX transaction level caching may be a pb with multiple repository @@ -529,9 +528,7 @@ else: del self.data['ecache'][eid] - # Tracking of entity added of removed in the transaction ################## - # - # Those are function to allows cheap call from client in other process. + # Tracking of entities added of removed in the transaction ################## def deleted_in_transaction(self, eid): """return True if the entity of the given eid is being deleted in the @@ -652,6 +649,7 @@ num = self.data.setdefault('tx_action_count', 0) + 1 self.data['tx_action_count'] = num return num + # db-api like interface ################################################### def source_defs(self): @@ -662,10 +660,9 @@ metas = self.repo.type_and_source_from_eid(eid, self) if asdict: return dict(zip(('type', 'source', 'extid', 'asource'), metas)) - # XXX :-1 for cw compat, use asdict=True for full information + # XXX :-1 for cw compat, use asdict=True for full information return metas[:-1] - def source_from_eid(self, eid): """return the source where the entity with id is located""" return self.repo.source_from_eid(eid, self) @@ -786,7 +783,7 @@ of None (not yet committing), 'precommit' (calling precommit event on operations), 'postcommit' (calling postcommit event on operations), 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error - has been raised during the transaction and so it must be rollbacked). + has been raised during the transaction and so it must be rolled back). .. automethod:: cubicweb.server.session.Session.commit .. automethod:: cubicweb.server.session.Session.rollback @@ -899,7 +896,7 @@ call `session.commit()` on normal exit. The `free_cnxset` will be given to rollback/commit methods to indicate - wether the connections set should be freed or not. + whether the connections set should be freed or not. """ return transaction(self, free_cnxset) @@ -1230,7 +1227,7 @@ return cstate = self.commit_state if cstate == 'uncommitable': - raise QueryError('transaction must be rollbacked') + raise QueryError('transaction must be rolled back') if cstate is not None: return # on rollback, an operation should have the following state @@ -1345,7 +1342,7 @@ self._closed = True tracker.close() self.rollback() - self.info('waiting for open transaction of session: %s', self) + self.debug('waiting for open transaction of session: %s', self) timeout = 10 pendings = tracker.wait(timeout) if pendings: diff -r 614762cdc357 -r 5f2c5eb1a820 server/sources/extlite.py --- a/server/sources/extlite.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/sources/extlite.py Thu Oct 10 13:26:11 2013 +0200 @@ -295,7 +295,7 @@ query, args, ex.args[0]) try: session.cnxset.connection(self.uri).rollback() - self.critical('transaction has been rollbacked') + self.critical('transaction has been rolled back') except Exception: pass raise diff -r 614762cdc357 -r 5f2c5eb1a820 server/sources/native.py --- a/server/sources/native.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/sources/native.py Thu Oct 10 13:26:11 2013 +0200 @@ -751,7 +751,7 @@ try: session.cnxset.connection(self.uri).rollback() if self.repo.config.mode != 'test': - self.critical('transaction has been rollbacked') + self.critical('transaction has been rolled back') except Exception as ex: pass if ex.__class__.__name__ == 'IntegrityError': @@ -795,7 +795,7 @@ try: session.cnxset.connection(self.uri).rollback() if self.repo.config.mode != 'test': - self.critical('transaction has been rollbacked') + self.critical('transaction has been rolled back') except Exception: pass raise diff -r 614762cdc357 -r 5f2c5eb1a820 server/sqlutils.py --- a/server/sqlutils.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/sqlutils.py Thu Oct 10 13:26:11 2013 +0200 @@ -378,7 +378,7 @@ def init_postgres_connexion(cnx): cnx.cursor().execute('SET TIME ZONE UTC') # commit is needed, else setting are lost if the connection is first - # rollbacked + # rolled back cnx.commit() postgres_hooks = SQL_CONNECT_HOOKS.setdefault('postgres', []) diff -r 614762cdc357 -r 5f2c5eb1a820 server/test/unittest_ldapsource.py diff -r 614762cdc357 -r 5f2c5eb1a820 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/test/unittest_migractions.py Thu Oct 10 13:26:11 2013 +0200 @@ -434,12 +434,12 @@ self.mh.commit() # unique_together test self.assertEqual(len(self.schema.eschema('Personne')._unique_together), 1) - self.assertItemsEqual(self.schema.eschema('Personne')._unique_together[0], + self.assertCountEqual(self.schema.eschema('Personne')._unique_together[0], ('nom', 'prenom', 'datenaiss')) rset = cursor.execute('Any C WHERE C is CWUniqueTogetherConstraint, C constraint_of ET, ET name "Personne"') self.assertEqual(len(rset), 1) relations = [r.name for r in rset.get_entity(0, 0).relations] - self.assertItemsEqual(relations, ('nom', 'prenom', 'datenaiss')) + self.assertCountEqual(relations, ('nom', 'prenom', 'datenaiss')) def _erqlexpr_rset(self, action, ertype): rql = 'RQLExpression X WHERE ET is CWEType, ET %s_permission X, ET name %%(name)s' % action diff -r 614762cdc357 -r 5f2c5eb1a820 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/test/unittest_querier.py Thu Oct 10 13:26:11 2013 +0200 @@ -702,7 +702,7 @@ rset = self.execute('Any X WHERE X is CWGroup', build_descr=0) rset.rows.sort() self.assertEqual(tuplify(rset.rows), [(2,), (3,), (4,), (5,)]) - self.assertEqual(rset.description, ()) + self.assertEqual(rset.description, []) def test_select_limit_offset(self): rset = self.execute('CWGroup X ORDERBY N LIMIT 2 WHERE X name N') diff -r 614762cdc357 -r 5f2c5eb1a820 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/test/unittest_repository.py Thu Oct 10 13:26:11 2013 +0200 @@ -137,7 +137,7 @@ self.assertTrue(self.execute('Any X WHERE X is CWGroup, X name "toto"')) with self.assertRaises(QueryError) as cm: self.commit() - self.assertEqual(str(cm.exception), 'transaction must be rollbacked') + self.assertEqual(str(cm.exception), 'transaction must be rolled back') self.rollback() self.assertFalse(self.execute('Any X WHERE X is CWGroup, X name "toto"')) @@ -154,7 +154,7 @@ self.assertTrue(self.execute('Any X WHERE X is CWGroup, X name "toto"')) with self.assertRaises(QueryError) as cm: self.commit() - self.assertEqual(str(cm.exception), 'transaction must be rollbacked') + self.assertEqual(str(cm.exception), 'transaction must be rolled back') self.rollback() self.assertFalse(self.execute('Any X WHERE X is CWGroup, X name "toto"')) diff -r 614762cdc357 -r 5f2c5eb1a820 server/test/unittest_storage.py --- a/server/test/unittest_storage.py Wed Oct 09 11:13:56 2013 +0200 +++ b/server/test/unittest_storage.py Thu Oct 10 13:26:11 2013 +0200 @@ -237,7 +237,7 @@ self.assertEqual(osp.splitext(new_path)[1], '.jpg') @tag('update', 'extension', 'rollback') - def test_bfss_update_with_different_extension_rollbacked(self): + def test_bfss_update_with_different_extension_rolled_back(self): # use self.session to use server-side cache f1 = self.session.create_entity('File', data=Binary('some data'), data_format=u'text/plain', data_name=u'foo.txt') diff -r 614762cdc357 -r 5f2c5eb1a820 setup.py --- a/setup.py Wed Oct 09 11:13:56 2013 +0200 +++ b/setup.py Thu Oct 10 13:26:11 2013 +0200 @@ -63,7 +63,7 @@ ext_modules = getattr(__pkginfo__, 'ext_modules', None) package_data = getattr(__pkginfo__, 'package_data', {}) -BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog') +BASE_BLACKLIST = ('CVS', 'dist', 'build', '__buildlog') IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc') diff -r 614762cdc357 -r 5f2c5eb1a820 skeleton/debian/rules.tmpl --- a/skeleton/debian/rules.tmpl Wed Oct 09 11:13:56 2013 +0200 +++ b/skeleton/debian/rules.tmpl Thu Oct 10 13:26:11 2013 +0200 @@ -37,7 +37,7 @@ dh_install -i dh_installchangelogs -i dh_installexamples -i - dh_installdocs -i + dh_installdocs -i README dh_installman -i dh_pysupport -i /usr/share/cubicweb dh_link -i diff -r 614762cdc357 -r 5f2c5eb1a820 test/unittest_entity.py --- a/test/unittest_entity.py Wed Oct 09 11:13:56 2013 +0200 +++ b/test/unittest_entity.py Thu Oct 10 13:26:11 2013 +0200 @@ -150,24 +150,24 @@ p1 = req.create_entity('Personne', nom=u'di') p2 = req.create_entity('Personne', nom=u'mascio') t = req.create_entity('Tag', name=u't0', tags=[]) - self.assertItemsEqual(t.tags, []) + self.assertCountEqual(t.tags, []) t = req.create_entity('Tag', name=u't1', tags=p1) - self.assertItemsEqual(t.tags, [p1]) + self.assertCountEqual(t.tags, [p1]) t = req.create_entity('Tag', name=u't2', tags=p1.eid) - self.assertItemsEqual(t.tags, [p1]) + self.assertCountEqual(t.tags, [p1]) t = req.create_entity('Tag', name=u't3', tags=[p1, p2.eid]) - self.assertItemsEqual(t.tags, [p1, p2]) + self.assertCountEqual(t.tags, [p1, p2]) def test_cw_instantiate_reverse_relation(self): req = self.request() t1 = req.create_entity('Tag', name=u't1') t2 = req.create_entity('Tag', name=u't2') p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=t1) - self.assertItemsEqual(p.reverse_tags, [t1]) + self.assertCountEqual(p.reverse_tags, [t1]) p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=t1.eid) - self.assertItemsEqual(p.reverse_tags, [t1]) + self.assertCountEqual(p.reverse_tags, [t1]) p = req.create_entity('Personne', nom=u'di mascio', reverse_tags=[t1, t2.eid]) - self.assertItemsEqual(p.reverse_tags, [t1, t2]) + self.assertCountEqual(p.reverse_tags, [t1, t2]) def test_fetch_rql(self): user = self.user() diff -r 614762cdc357 -r 5f2c5eb1a820 test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Wed Oct 09 11:13:56 2013 +0200 +++ b/test/unittest_rqlrewrite.py Thu Oct 10 13:26:11 2013 +0200 @@ -438,30 +438,30 @@ u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)") def test_rqlexpr_not_relation_1_1(self): - constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') + constraint = ERQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') self.assertEqual(rqlst.as_string(), u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') def test_rqlexpr_not_relation_1_2(self): - constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') + constraint = ERQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') rewrite(rqlst, {('A', 'X'): (constraint,)}, {}, 'X') self.assertEqual(rqlst.as_string(), u'Any A WHERE NOT EXISTS(A documented_by C, C is Card), A is Affaire, EXISTS(A owned_by B, B login "hop", B is CWUser)') def test_rqlexpr_not_relation_2(self): - constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') + constraint = ERQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = rqlhelper.parse('Affaire A WHERE NOT A documented_by C', annotate=False) rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') self.assertEqual(rqlst.as_string(), u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') def test_rqlexpr_multiexpr_outerjoin(self): - c1 = RRQLExpression('X owned_by Z, Z login "hop"', 'X') - c2 = RRQLExpression('X owned_by Z, Z login "hip"', 'X') - c3 = RRQLExpression('X owned_by Z, Z login "momo"', 'X') + c1 = ERQLExpression('X owned_by Z, Z login "hop"', 'X') + c2 = ERQLExpression('X owned_by Z, Z login "hip"', 'X') + c3 = ERQLExpression('X owned_by Z, Z login "momo"', 'X') rqlst = rqlhelper.parse('Any A WHERE A documented_by C?', annotate=False) rewrite(rqlst, {('C', 'X'): (c1, c2, c3)}, {}, 'X') self.assertEqual(rqlst.as_string(), diff -r 614762cdc357 -r 5f2c5eb1a820 test/unittest_schema.py --- a/test/unittest_schema.py Wed Oct 09 11:13:56 2013 +0200 +++ b/test/unittest_schema.py Thu Oct 10 13:26:11 2013 +0200 @@ -132,6 +132,8 @@ self.assertRaises(RQLSyntaxError, ERQLExpression, '1') expr = ERQLExpression('X travaille S, S owned_by U') self.assertEqual(str(expr), 'Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s') + expr = ERQLExpression('X foo S, S bar U, X baz XE, S quux SE HAVING XE > SE') + self.assertEqual(str(expr), 'Any X WHERE X foo S, S bar U, X baz XE, S quux SE, X eid %(x)s, U eid %(u)s HAVING XE > SE') def test_rrqlexpression(self): self.assertRaises(Exception, RRQLExpression, '1') diff -r 614762cdc357 -r 5f2c5eb1a820 utils.py --- a/utils.py Wed Oct 09 11:13:56 2013 +0200 +++ b/utils.py Thu Oct 10 13:26:11 2013 +0200 @@ -195,6 +195,8 @@ if isinstance(other, RepeatList): return other._size == self._size and other._item == self._item return self[:] == other + # py3k future warning "Overriding __eq__ blocks inheritance of __hash__ in 3.x" + # is annoying but won't go away because we don't want to hash() the repeatlist def pop(self, i): self._size -= 1 diff -r 614762cdc357 -r 5f2c5eb1a820 web/formfields.py --- a/web/formfields.py Wed Oct 09 11:13:56 2013 +0200 +++ b/web/formfields.py Thu Oct 10 13:26:11 2013 +0200 @@ -553,7 +553,7 @@ widget.attrs.setdefault('maxlength', self.max_length) def init_text_area(self, widget): - if self.max_length < 513: + if self.max_length and self.max_length < 513: widget.attrs.setdefault('cols', 60) widget.attrs.setdefault('rows', 5) diff -r 614762cdc357 -r 5f2c5eb1a820 web/test/unittest_uicfg.py --- a/web/test/unittest_uicfg.py Wed Oct 09 11:13:56 2013 +0200 +++ b/web/test/unittest_uicfg.py Thu Oct 10 13:26:11 2013 +0200 @@ -85,15 +85,15 @@ def test_uihelper_hide_fields(self): # original conf : in_group is edited in 'attributes' section everywhere section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject') - self.assertItemsEqual(section_conf, ['main_attributes', 'muledit_attributes']) + self.assertCountEqual(section_conf, ['main_attributes', 'muledit_attributes']) # hide field in main form uihelper.hide_fields('CWUser', ('login', 'in_group')) section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject') - self.assertItemsEqual(section_conf, ['main_hidden', 'muledit_attributes']) + self.assertCountEqual(section_conf, ['main_hidden', 'muledit_attributes']) # hide field in muledit form uihelper.hide_fields('CWUser', ('login', 'in_group'), formtype='muledit') section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject') - self.assertItemsEqual(section_conf, ['main_hidden', 'muledit_hidden']) + self.assertCountEqual(section_conf, ['main_hidden', 'muledit_hidden']) @tag('uihelper', 'hidden', 'formconfig') def test_uihelper_formconfig(self): @@ -103,7 +103,7 @@ hidden = ('in_group',) fields_order = ('login', 'firstname') section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject') - self.assertItemsEqual(section_conf, ['main_hidden', 'muledit_attributes']) + self.assertCountEqual(section_conf, ['main_hidden', 'muledit_attributes']) self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1}) diff -r 614762cdc357 -r 5f2c5eb1a820 web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Wed Oct 09 11:13:56 2013 +0200 +++ b/web/test/unittest_views_basecontrollers.py Thu Oct 10 13:26:11 2013 +0200 @@ -397,7 +397,7 @@ path, params = self.expect_redirect_handle_request(req, 'edit') usergroups = [gname for gname, in self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})] - self.assertItemsEqual(usergroups, ['managers', 'test']) + self.assertCountEqual(usergroups, ['managers', 'test']) self.assertEqual(get_pending_inserts(req), []) def test_req_pending_delete(self): @@ -408,14 +408,14 @@ usergroups = [gname for gname, in self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})] # just make sure everything was set correctly - self.assertItemsEqual(usergroups, ['managers', 'test']) + self.assertCountEqual(usergroups, ['managers', 'test']) # now try to delete the relation req = self.request(**req_form(user)) req.session.data['pending_delete'] = set([(user.eid, 'in_group', groupeid)]) path, params = self.expect_redirect_handle_request(req, 'edit') usergroups = [gname for gname, in self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})] - self.assertItemsEqual(usergroups, ['managers']) + self.assertCountEqual(usergroups, ['managers']) self.assertEqual(get_pending_deletes(req), []) def test_redirect_apply_button(self): @@ -715,7 +715,7 @@ def test_remote_add_existing_tag(self): self.remote_call('tag_entity', self.john.eid, ['python']) - self.assertItemsEqual( + self.assertCountEqual( [tname for tname, in self.execute('Any N WHERE T is Tag, T name N')], ['python', 'cubicweb']) self.assertEqual( @@ -724,7 +724,7 @@ def test_remote_add_new_tag(self): self.remote_call('tag_entity', self.john.eid, ['javascript']) - self.assertItemsEqual( + self.assertCountEqual( [tname for tname, in self.execute('Any N WHERE T is Tag, T name N')], ['python', 'cubicweb', 'javascript']) self.assertEqual(