# HG changeset patch # User Pierre-Yves David # Date 1364470874 -3600 # Node ID 064231ce93d52f93cbceaacfdb6709338b76a08d # Parent d95a79c2687c318a52015a39bdd7688414e497c6# Parent c4aa23af0baab02ef8dfa25ec3cec8f9524108d3 merge 3.16.x fix in 3.17.x diff -r c4aa23af0baa -r 064231ce93d5 __init__.py --- a/__init__.py Thu Mar 28 12:32:26 2013 +0100 +++ b/__init__.py Thu Mar 28 12:41:14 2013 +0100 @@ -38,10 +38,10 @@ import sys, os, logging from StringIO import StringIO +from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods from yams.constraints import BASE_CONVERTERS - if os.environ.get('APYCOT_ROOT'): logging.basicConfig(level=logging.CRITICAL) else: @@ -57,8 +57,9 @@ from logilab.common.registry import ObjectNotFound, NoSelectableObject, RegistryNotFound # convert eid to the right type, raise ValueError if it's not a valid eid -typed_eid = int - +@deprecated('[3.17] typed_eid() was removed. replace it with int() when needed.') +def typed_eid(eid): + return int(eid) #def log_thread(f, w, a): # print f.f_code.co_filename, f.f_code.co_name diff -r c4aa23af0baa -r 064231ce93d5 __pkginfo__.py --- a/__pkginfo__.py Thu Mar 28 12:32:26 2013 +0100 +++ b/__pkginfo__.py Thu Mar 28 12:41:14 2013 +0100 @@ -43,7 +43,7 @@ 'logilab-common': '>= 0.59.0', 'logilab-mtconverter': '>= 0.8.0', 'rql': '>= 0.31.2', - 'yams': '>= 0.36.0', + 'yams': '>= 0.37.0', #gettext # for xgettext, msgcat, etc... # web dependancies 'simplejson': '>= 2.0.9', diff -r c4aa23af0baa -r 064231ce93d5 dataimport.py --- a/dataimport.py Thu Mar 28 12:32:26 2013 +0100 +++ b/dataimport.py Thu Mar 28 12:41:14 2013 +0100 @@ -538,76 +538,6 @@ def nb_inserted_relations(self): return len(self.relations) - @deprecated("[3.7] index support will disappear") - def build_index(self, name, type, func=None, can_be_empty=False): - """build internal index for further search""" - index = {} - if func is None or not callable(func): - func = lambda x: x['eid'] - for eid in self.types[type]: - index.setdefault(func(self.eids[eid]), []).append(eid) - if not can_be_empty: - assert index, "new index '%s' cannot be empty" % name - self.indexes[name] = index - - @deprecated("[3.7] index support will disappear") - def build_rqlindex(self, name, type, key, rql, rql_params=False, - func=None, can_be_empty=False): - """build an index by rql query - - rql should return eid in first column - ctl.store.build_index('index_name', 'users', 'login', 'Any U WHERE U is CWUser') - """ - self.types[type] = [] - rset = self.rql(rql, rql_params or {}) - if not can_be_empty: - assert rset, "new index type '%s' cannot be empty (0 record found)" % type - for entity in rset.entities(): - getattr(entity, key) # autopopulate entity with key attribute - self.eids[entity.eid] = dict(entity) - if entity.eid not in self.types[type]: - self.types[type].append(entity.eid) - - # Build index with specified key - func = lambda x: x[key] - self.build_index(name, type, func, can_be_empty=can_be_empty) - - @deprecated("[3.7] index support will disappear") - def fetch(self, name, key, unique=False, decorator=None): - """index fetcher method - - decorator is a callable method or an iterator of callable methods (usually a lambda function) - decorator=lambda x: x[:1] (first value is returned) - decorator=lambda x: x.lower (lowercased value is returned) - - decorator is handy when you want to improve index keys but without - changing the original field - - Same check functions can be reused here. - """ - eids = self.indexes[name].get(key, []) - if decorator is not None: - if not hasattr(decorator, '__iter__'): - decorator = (decorator,) - for f in decorator: - eids = f(eids) - if unique: - assert len(eids) == 1, u'expected a single one value for key "%s" in index "%s". Got %i' % (key, name, len(eids)) - eids = eids[0] - return eids - - @deprecated("[3.7] index support will disappear") - def find(self, type, key, value): - for idx in self.types[type]: - item = self.items[idx] - if item[key] == value: - yield item - - @deprecated("[3.7] checkpoint() deprecated. use commit() instead") - def checkpoint(self): - self.commit() - - class RQLObjectStore(ObjectStore): """ObjectStore that works with an actual RQL repository (production mode)""" _rql = None # bw compat @@ -630,10 +560,6 @@ self.session = session self._commit = commit or session.commit - @deprecated("[3.7] checkpoint() deprecated. use commit() instead") - def checkpoint(self): - self.commit() - def commit(self): txuuid = self._commit() self.session.set_cnxset() @@ -809,8 +735,8 @@ self._nb_inserted_relations = 0 self.rql = session.execute # deactivate security - session.set_read_security(False) - session.set_write_security(False) + session.read_security = False + session.write_security = False def create_entity(self, etype, **kwargs): for k, v in kwargs.iteritems(): diff -r c4aa23af0baa -r 064231ce93d5 debian/control --- a/debian/control Thu Mar 28 12:32:26 2013 +0100 +++ b/debian/control Thu Mar 28 12:41:14 2013 +0100 @@ -16,7 +16,7 @@ python-unittest2, python-logilab-mtconverter, python-rql, - python-yams, + python-yams (>= 0.37), python-lxml, Standards-Version: 3.9.1 Homepage: http://www.cubicweb.org diff -r c4aa23af0baa -r 064231ce93d5 devtools/fake.py --- a/devtools/fake.py Thu Mar 28 12:32:26 2013 +0100 +++ b/devtools/fake.py Thu Mar 28 12:41:14 2013 +0100 @@ -163,10 +163,6 @@ # for use with enabled_security context manager read_security = write_security = True - def init_security(self, *args): - return None, None - def reset_security(self, *args): - return class FakeRepo(object): querier = None diff -r c4aa23af0baa -r 064231ce93d5 devtools/repotest.py --- a/devtools/repotest.py Thu Mar 28 12:32:26 2013 +0100 +++ b/devtools/repotest.py Thu Mar 28 12:41:14 2013 +0100 @@ -262,8 +262,8 @@ u = self.repo._build_user(self.session, self.session.user.eid) u._groups = set(groups) s = Session(u, self.repo) - s._threaddata.cnxset = self.cnxset - s._threaddata.ctx_count = 1 + s._tx.cnxset = self.cnxset + s._tx.ctx_count = 1 # register session to ensure it gets closed self._dumb_sessions.append(s) return s @@ -311,7 +311,8 @@ del self.repo.sources_by_uri[source.uri] undo_monkey_patch() for session in self._dumb_sessions: - session._threaddata.cnxset = None + if session._tx.cnxset is not None: + session._tx.cnxset = None session.close() def _prepare_plan(self, rql, kwargs=None): diff -r c4aa23af0baa -r 064231ce93d5 doc/3.17.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/3.17.rst Thu Mar 28 12:41:14 2013 +0100 @@ -0,0 +1,34 @@ +What's new in CubicWeb 3.17? +============================ + +New functionalities +-------------------- + +* add a command to compare db schema and file system schema + (see `#464991 `_) + +* Add CubicWebRequestBase.content with the content of the HTTP request (see #2742453) + (see `#2742453 `_) + + +API changes +----------- + +* The SIOC views and adapters have been removed from CubicWeb and moved to the + `sioc` cube. + +* The web page embedding views and adapters have been removed from CubicWeb and + moved to the `embed` cube. + +* drop typed_eid() in favour of int() (see `#2742462 `_) + + + +Deprecated Code Drops +---------------------- + +* The progress views and adapters have been removed from CubicWeb. These + classes were deprecated since 3.14.0. They are still available in the + `iprogress` cube. + +* API deprecated since 3.7 have been dropped. diff -r c4aa23af0baa -r 064231ce93d5 doc/book/en/devrepo/repo/sessions.rst --- a/doc/book/en/devrepo/repo/sessions.rst Thu Mar 28 12:32:26 2013 +0100 +++ b/doc/book/en/devrepo/repo/sessions.rst Thu Mar 28 12:41:14 2013 +0100 @@ -199,3 +199,8 @@ if hasattr(req.cnx, 'foo_user') and req.foo_user: return 1 return 0 + +Full API Session +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: cubicweb.server.session.Session diff -r c4aa23af0baa -r 064231ce93d5 doc/tools/pyjsrest.py --- a/doc/tools/pyjsrest.py Thu Mar 28 12:32:26 2013 +0100 +++ b/doc/tools/pyjsrest.py Thu Mar 28 12:41:14 2013 +0100 @@ -136,7 +136,6 @@ 'cubicweb.preferences', 'cubicweb.edition', 'cubicweb.reledit', - 'cubicweb.iprogress', 'cubicweb.rhythm', 'cubicweb.gmap', 'cubicweb.timeline-ext', diff -r c4aa23af0baa -r 064231ce93d5 entities/__init__.py --- a/entities/__init__.py Thu Mar 28 12:32:26 2013 +0100 +++ b/entities/__init__.py Thu Mar 28 12:41:14 2013 +0100 @@ -24,7 +24,7 @@ from logilab.common.deprecation import deprecated from logilab.common.decorators import cached -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized from cubicweb.entity import Entity diff -r c4aa23af0baa -r 064231ce93d5 entities/adapters.py --- a/entities/adapters.py Thu Mar 28 12:32:26 2013 +0100 +++ b/entities/adapters.py Thu Mar 28 12:41:14 2013 +0100 @@ -404,117 +404,3 @@ "%(cls)s is deprecated") % {'cls': cls.__name__} warn(msg, DeprecationWarning, stacklevel=2) return type.__call__(cls, *args, **kwargs) - - -class IProgressAdapter(view.EntityAdapter): - """something that has a cost, a state and a progression. - - You should at least override progress_info an in_progress methods on - concrete implementations. - """ - __metaclass__ = adapter_deprecated - __deprecation_warning__ = '[3.14] IProgressAdapter has been moved to iprogress cube' - __needs_bw_compat__ = True - __regid__ = 'IProgress' - __select__ = implements(IProgress, warn=False) # XXX for bw compat, should be abstract - - @property - @view.implements_adapter_compat('IProgress') - def cost(self): - """the total cost""" - return self.progress_info()['estimated'] - - @property - @view.implements_adapter_compat('IProgress') - def revised_cost(self): - return self.progress_info().get('estimatedcorrected', self.cost) - - @property - @view.implements_adapter_compat('IProgress') - def done(self): - """what is already done""" - return self.progress_info()['done'] - - @property - @view.implements_adapter_compat('IProgress') - def todo(self): - """what remains to be done""" - return self.progress_info()['todo'] - - @view.implements_adapter_compat('IProgress') - def progress_info(self): - """returns a dictionary describing progress/estimated cost of the - version. - - - mandatory keys are (''estimated', 'done', 'todo') - - - optional keys are ('notestimated', 'notestimatedcorrected', - 'estimatedcorrected') - - 'noestimated' and 'notestimatedcorrected' should default to 0 - 'estimatedcorrected' should default to 'estimated' - """ - raise NotImplementedError - - @view.implements_adapter_compat('IProgress') - def finished(self): - """returns True if status is finished""" - return not self.in_progress() - - @view.implements_adapter_compat('IProgress') - def in_progress(self): - """returns True if status is not finished""" - raise NotImplementedError - - @view.implements_adapter_compat('IProgress') - def progress(self): - """returns the % progress of the task item""" - try: - return 100. * self.done / self.revised_cost - except ZeroDivisionError: - # total cost is 0 : if everything was estimated, task is completed - if self.progress_info().get('notestimated'): - return 0. - return 100 - - @view.implements_adapter_compat('IProgress') - def progress_class(self): - return '' - - -class IMileStoneAdapter(IProgressAdapter): - __metaclass__ = adapter_deprecated - __deprecation_warning__ = '[3.14] IMileStoneAdapter has been moved to iprogress cube' - __needs_bw_compat__ = True - __regid__ = 'IMileStone' - __select__ = implements(IMileStone, warn=False) # XXX for bw compat, should be abstract - - parent_type = None # specify main task's type - - @view.implements_adapter_compat('IMileStone') - def get_main_task(self): - """returns the main ITask entity""" - raise NotImplementedError - - @view.implements_adapter_compat('IMileStone') - def initial_prevision_date(self): - """returns the initial expected end of the milestone""" - raise NotImplementedError - - @view.implements_adapter_compat('IMileStone') - def eta_date(self): - """returns expected date of completion based on what remains - to be done - """ - raise NotImplementedError - - @view.implements_adapter_compat('IMileStone') - def completion_date(self): - """returns date on which the subtask has been completed""" - raise NotImplementedError - - @view.implements_adapter_compat('IMileStone') - def contractors(self): - """returns the list of persons supposed to work on this task""" - raise NotImplementedError - diff -r c4aa23af0baa -r 064231ce93d5 entity.py --- a/entity.py Thu Mar 28 12:32:26 2013 +0100 +++ b/entity.py Thu Mar 28 12:41:14 2013 +0100 @@ -33,11 +33,10 @@ from rql.nodes import (Not, VariableRef, Constant, make_relation, Relation as RqlRelation) -from cubicweb import Unauthorized, typed_eid, neg_role +from cubicweb import Unauthorized, neg_role from cubicweb.utils import support_args from cubicweb.rset import ResultSet from cubicweb.appobject import AppObject -from cubicweb.req import _check_cw_unsafe from cubicweb.schema import (RQLVocabularyConstraint, RQLConstraint, GeneratedConstraint) from cubicweb.rqlrewrite import RQLRewriter @@ -627,7 +626,7 @@ meaning that the entity has to be created """ try: - typed_eid(self.eid) + int(self.eid) return True except (ValueError, TypeError): return False @@ -1287,7 +1286,6 @@ an entity or eid, a list of entities or eids, or None (meaning that all relations of the given type from or to this object should be deleted). """ - _check_cw_unsafe(kwargs) assert kwargs assert self.cw_is_saved(), "should not call set_attributes while entity "\ "hasn't been saved yet" @@ -1397,10 +1395,6 @@ @deprecated('[3.10] use entity.cw_attr_cache[attr]') def __getitem__(self, key): - if key == 'eid': - warn('[3.7] entity["eid"] is deprecated, use entity.eid instead', - DeprecationWarning, stacklevel=2) - return self.eid return self.cw_attr_cache[key] @deprecated('[3.10] use entity.cw_attr_cache.get(attr[, default])') @@ -1424,15 +1418,10 @@ the attribute to skip_security since we don't want to check security for such attributes set by hooks. """ - if attr == 'eid': - warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead', - DeprecationWarning, stacklevel=2) - self.eid = value - else: - try: - self.cw_edited[attr] = value - except AttributeError: - self.cw_attr_cache[attr] = value + try: + self.cw_edited[attr] = value + except AttributeError: + self.cw_attr_cache[attr] = value @deprecated('[3.10] use del entity.cw_edited[attr]') def __delitem__(self, attr): diff -r c4aa23af0baa -r 064231ce93d5 etwist/request.py --- a/etwist/request.py Thu Mar 28 12:32:26 2013 +0100 +++ b/etwist/request.py Thu Mar 28 12:41:14 2013 +0100 @@ -39,6 +39,7 @@ self.form[key] = (name, stream) else: self.form[key] = (unicode(name, self.encoding), stream) + self.content = self._twreq.content # stream def http_method(self): """returns 'POST', 'GET', 'HEAD', etc.""" diff -r c4aa23af0baa -r 064231ce93d5 etwist/test/data/views.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etwist/test/data/views.py Thu Mar 28 12:41:14 2013 +0100 @@ -0,0 +1,29 @@ +# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""only for unit tests !""" + +from cubicweb.view import View +from cubicweb.predicates import match_http_method + +class PutView(View): + __regid__ = 'put' + __select__ = match_http_method('PUT') + binary = True + + def call(self): + self.w(self._cw.content.read()) diff -r c4aa23af0baa -r 064231ce93d5 etwist/test/unittest_server.py --- a/etwist/test/unittest_server.py Thu Mar 28 12:32:26 2013 +0100 +++ b/etwist/test/unittest_server.py Thu Mar 28 12:41:14 2013 +0100 @@ -19,6 +19,7 @@ import os, os.path as osp, glob from cubicweb.devtools.testlib import CubicWebTC +from cubicweb.devtools.httptest import CubicWebServerTC from cubicweb.etwist.server import host_prefixed_baseurl @@ -53,6 +54,13 @@ self._check('http://localhost:8080/hg/', 'code.cubicweb.org', 'http://localhost:8080/hg/') + +class ETwistHTTPTC(CubicWebServerTC): + def test_put_content(self): + body = 'hop' + response = self.web_request('?vid=put', method='PUT', body=body) + self.assertEqual(body, response.body) + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r c4aa23af0baa -r 064231ce93d5 interfaces.py --- a/interfaces.py Thu Mar 28 12:32:26 2013 +0100 +++ b/interfaces.py Thu Mar 28 12:41:14 2013 +0100 @@ -148,34 +148,6 @@ def marker_icon(self): """returns the icon that should be used as the marker""" -# XXX deprecates in favor of ISIOCItemAdapter -class ISiocItem(Interface): - """interface for entities which may be represented as an ISIOC item""" - - def isioc_content(self): - """return item's content""" - - def isioc_container(self): - """return container entity""" - - def isioc_type(self): - """return container type (post, BlogPost, MailMessage)""" - - def isioc_replies(self): - """return replies items""" - - def isioc_topics(self): - """return topics items""" - -# XXX deprecates in favor of ISIOCContainerAdapter -class ISiocContainer(Interface): - """interface for entities which may be represented as an ISIOC container""" - - def isioc_type(self): - """return container type (forum, Weblog, MailingList)""" - - def isioc_items(self): - """return contained items""" # XXX deprecates in favor of IEmailableAdapter class IFeed(Interface): diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.3.5_Any.py --- a/misc/migration/3.3.5_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.3.5_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,8 +1,1 @@ -# some entities have been added before schema entities, fix the 'is' and -# 'is_instance_of' relations -for rtype in ('is', 'is_instance_of'): - sql('INSERT INTO %s_relation ' - 'SELECT X.eid, ET.cw_eid FROM entities as X, cw_CWEType as ET ' - 'WHERE X.type=ET.cw_name AND NOT EXISTS(' - ' SELECT 1 from is_relation ' - ' WHERE eid_from=X.eid AND eid_to=ET.cw_eid)' % rtype) +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.4.0_Any.py --- a/misc/migration/3.4.0_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.4.0_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,2 +1,1 @@ -drop_attribute('CWEType', 'meta') -drop_attribute('CWRType', 'meta') +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.4.0_common.py --- a/misc/migration/3.4.0_common.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.4.0_common.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,6 +1,1 @@ -from os.path import join -from cubicweb.toolsutils import create_dir - -option_renamed('pyro-application-id', 'pyro-instance-id') - -create_dir(join(config.appdatahome, 'backup')) +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.4.3_Any.py --- a/misc/migration/3.4.3_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.4.3_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,2 +1,1 @@ -# sync and restart to make sure cwuri does not appear in forms -sync_schema_props_perms() +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.5.0_Any.py --- a/misc/migration/3.5.0_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.5.0_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,10 +1,1 @@ -add_relation_type('prefered_form') - -rql('SET X prefered_form Y WHERE Y canonical TRUE, X identical_to Y') -commit() - -drop_attribute('EmailAddress', 'canonical') -drop_relation_definition('EmailAddress', 'identical_to', 'EmailAddress') - -if 'see_also' in schema: - sync_schema_props_perms('see_also', syncprops=False, syncrdefs=False) +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.5.10_Any.py --- a/misc/migration/3.5.10_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.5.10_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,5 +1,1 @@ -sync_schema_props_perms('state_of') -sync_schema_props_perms('transition_of') -for etype in ('State', 'BaseTransition', 'Transition', 'WorkflowTransition'): - sync_schema_props_perms((etype, 'name', 'String')) - +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.5.3_Any.py --- a/misc/migration/3.5.3_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.5.3_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,7 +1,1 @@ -# type attribute might already be there if migrating from -# version < 3.5 to version >= 3.5.3, BaseTransition being added -# in bootstrap_migration -if versions_map['cubicweb'][0] >= (3, 5, 0): - add_attribute('BaseTransition', 'type') - sync_schema_props_perms('state_of') - sync_schema_props_perms('transition_of') +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/3.6.1_Any.py --- a/misc/migration/3.6.1_Any.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/3.6.1_Any.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,2 +1,1 @@ -sync_schema_props_perms(syncprops=False) -sync_schema_props_perms('destination_state', syncperms=False) +raise NotImplementedError("Cannot migrate such an old version. Use intermediate Cubiweb version (try 3.16.x)") diff -r c4aa23af0baa -r 064231ce93d5 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu Mar 28 12:32:26 2013 +0100 +++ b/misc/migration/bootstrapmigration_repository.py Thu Mar 28 12:41:14 2013 +0100 @@ -34,6 +34,20 @@ ss.execschemarql(rql, rdef, ss.rdef2rql(rdef, CSTRMAP, groupmap=None)) commit(ask_confirm=False) +if applcubicwebversion < (3, 17, 0) and cubicwebversion >= (3, 17, 0): + try: + add_cube('sioc', update_database=False) + except ImportError: + if not confirm('In cubicweb 3.17 sioc views have been moved to the sioc ' + 'cube, which is not installed. Continue anyway?'): + raise + try: + add_cube('embed', update_database=False) + except ImportError: + if not confirm('In cubicweb 3.17 embedding views have been moved to the embed ' + 'cube, which is not installed. Continue anyway?'): + raise + if applcubicwebversion <= (3, 13, 0) and cubicwebversion >= (3, 13, 1): sql('ALTER TABLE entities ADD asource VARCHAR(64)') sql('UPDATE entities SET asource=cw_name ' diff -r c4aa23af0baa -r 064231ce93d5 predicates.py --- a/predicates.py Thu Mar 28 12:32:26 2013 +0100 +++ b/predicates.py Thu Mar 28 12:41:14 2013 +0100 @@ -1470,6 +1470,15 @@ return frozenset(req.form) +class match_http_method(ExpectedValuePredicate): + """Return non-zero score if one of the HTTP methods specified as + initializer arguments is the HTTP method of the request (GET, POST, ...). + """ + + def __call__(self, cls, req, **kwargs): + return int(req.http_method() in self.expected) + + class match_edited_type(ExpectedValuePredicate): """return non-zero if main edited entity type is the one specified as initializer argument, or is among initializer arguments if `mode` == 'any'. diff -r c4aa23af0baa -r 064231ce93d5 req.py --- a/req.py Thu Mar 28 12:32:26 2013 +0100 +++ b/req.py Thu Mar 28 12:41:14 2013 +0100 @@ -29,7 +29,7 @@ from logilab.common.deprecation import deprecated from logilab.common.date import ustrftime, strptime, todate, todatetime -from cubicweb import Unauthorized, NoSelectableObject, typed_eid, uilib +from cubicweb import Unauthorized, NoSelectableObject, uilib from cubicweb.rset import ResultSet ONESECOND = timedelta(0, 1, 0) @@ -38,12 +38,6 @@ class FindEntityError(Exception): """raised when find_one_entity() can not return one and only one entity""" -def _check_cw_unsafe(kwargs): - if kwargs.pop('_cw_unsafe', False): - warn('[3.7] _cw_unsafe argument is deprecated, now unsafe by ' - 'default, control it using cw_[read|write]_security.', - DeprecationWarning, stacklevel=3) - class Cache(dict): def __init__(self): super(Cache, self).__init__() @@ -114,7 +108,7 @@ (we have the eid, we can suppose it exists and user has access to the entity) """ - eid = typed_eid(eid) + eid = int(eid) if etype is None: etype = self.describe(eid)[0] rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid}, @@ -154,7 +148,6 @@ ... works_for=c) """ - _check_cw_unsafe(kwargs) cls = self.vreg['etypes'].etype_class(etype) return cls.cw_instantiate(self.execute, **kwargs) diff -r c4aa23af0baa -r 064231ce93d5 rqlrewrite.py --- a/rqlrewrite.py Thu Mar 28 12:32:26 2013 +0100 +++ b/rqlrewrite.py Thu Mar 28 12:41:14 2013 +0100 @@ -30,7 +30,7 @@ from logilab.common import tempattr from logilab.common.graph import has_path -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized def add_types_restriction(schema, rqlst, newroot=None, solutions=None): @@ -220,7 +220,7 @@ vi = {} self.varinfos.append(vi) try: - vi['const'] = typed_eid(selectvar) + vi['const'] = int(selectvar) vi['rhs_rels'] = vi['lhs_rels'] = {} except ValueError: try: diff -r c4aa23af0baa -r 064231ce93d5 selectors.py --- a/selectors.py Thu Mar 28 12:32:26 2013 +0100 +++ b/selectors.py Thu Mar 28 12:41:14 2013 +0100 @@ -35,8 +35,6 @@ EClassSelector = class_renamed('EClassSelector', EClassPredicate) EntitySelector = class_renamed('EntitySelector', EntityPredicate) -# XXX pre 3.7? bw compat - class on_transition(is_in_state): """Return 1 if entity is in one of the transitions given as argument list diff -r c4aa23af0baa -r 064231ce93d5 server/migractions.py --- a/server/migractions.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/migractions.py Thu Mar 28 12:41:14 2013 +0100 @@ -300,8 +300,8 @@ if self.config is not None: session = self.repo._get_session(self.cnx.sessionid) if session.cnxset is None: - session.set_read_security(False) - session.set_write_security(False) + session.read_security = False + session.write_security = False session.set_cnxset() return session # no access to session on remote instance @@ -1515,14 +1515,6 @@ if commit: self.commit() - @deprecated("[3.7] use session.disable_hook_categories('integrity')") - def cmd_deactivate_verification_hooks(self): - self.session.disable_hook_categories('integrity') - - @deprecated("[3.7] use session.enable_hook_categories('integrity')") - def cmd_reactivate_verification_hooks(self): - self.session.enable_hook_categories('integrity') - @deprecated("[3.15] use rename_relation_type(oldname, newname)") def cmd_rename_relation(self, oldname, newname, commit=True): self.cmd_rename_relation_type(oldname, newname, commit) diff -r c4aa23af0baa -r 064231ce93d5 server/querier.py --- a/server/querier.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/querier.py Thu Mar 28 12:41:14 2013 +0100 @@ -31,7 +31,7 @@ from yams import BASE_TYPES from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid -from cubicweb import Binary, server, typed_eid +from cubicweb import Binary, server from cubicweb.rset import ResultSet from cubicweb.utils import QueryCache, RepeatList @@ -391,7 +391,7 @@ for var in rqlst.defined_vars.itervalues(): if var.stinfo['constnode'] is not None: eid = var.stinfo['constnode'].eval(self.args) - varkwargs[var.name] = typed_eid(eid) + varkwargs[var.name] = int(eid) # dictionary of variables restricted for security reason localchecks = {} restricted_vars = set() @@ -563,11 +563,11 @@ 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, basestring): - subj = typed_eid(subj) + subj = int(subj) elif not isinstance(subj, (int, long)): subj = subj.entity.eid if isinstance(obj, basestring): - obj = typed_eid(obj) + obj = int(obj) elif not isinstance(obj, (int, long)): obj = obj.entity.eid if repo.schema.rschema(rtype).inlined: diff -r c4aa23af0baa -r 064231ce93d5 server/repository.py --- a/server/repository.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/repository.py Thu Mar 28 12:41:14 2013 +0100 @@ -50,7 +50,7 @@ UnknownEid, AuthenticationError, ExecutionError, ETypeNotSupportedBySources, MultiSourcesError, BadConnectionId, Unauthorized, ValidationError, - RepositoryError, UniqueTogetherError, typed_eid, onevent) + RepositoryError, UniqueTogetherError, onevent) from cubicweb import cwvreg, schema, server from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources from cubicweb.server.session import Session, InternalSession, InternalManager @@ -844,7 +844,7 @@ self.debug('begin commit for session %s', sessionid) try: session = self._get_session(sessionid) - session.set_tx_data(txid) + session.set_tx(txid) return session.commit() except (ValidationError, Unauthorized): raise @@ -857,7 +857,7 @@ self.debug('begin rollback for session %s', sessionid) try: session = self._get_session(sessionid) - session.set_tx_data(txid) + session.set_tx(txid) session.rollback() except Exception: self.exception('unexpected error') @@ -1005,7 +1005,7 @@ except KeyError: raise BadConnectionId('No such session %s' % sessionid) if setcnxset: - session.set_tx_data(txid) # must be done before set_cnxset + session.set_tx(txid) # must be done before set_cnxset session.set_cnxset() return session @@ -1018,7 +1018,7 @@ uri)` for the entity of the given `eid` """ try: - eid = typed_eid(eid) + eid = int(eid) except ValueError: raise UnknownEid(eid) try: @@ -1046,7 +1046,7 @@ rqlcache = self.querier._rql_cache for eid in eids: try: - etype, uri, extid, auri = etcache.pop(typed_eid(eid)) # may be a string in some cases + etype, uri, extid, auri = etcache.pop(int(eid)) # may be a string in some cases rqlcache.pop( ('%s X WHERE X eid %s' % (etype, eid),), None) extidcache.pop((extid, uri), None) except KeyError: @@ -1075,7 +1075,7 @@ key, args[key])) cachekey.append(etype) # ensure eid is correctly typed in args - args[key] = typed_eid(args[key]) + args[key] = int(args[key]) return tuple(cachekey) def eid2extid(self, source, eid, session=None): @@ -1168,7 +1168,7 @@ hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid) self.system_source.delete_info_multi(session, [entity], uri) if source.should_call_hooks: - session._threaddata.pending_operations = pending_operations + session._tx.pending_operations = pending_operations raise def add_info(self, session, entity, source, extid=None, complete=True): diff -r c4aa23af0baa -r 064231ce93d5 server/schemaserial.py --- a/server/schemaserial.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/schemaserial.py Thu Mar 28 12:41:14 2013 +0100 @@ -26,7 +26,7 @@ from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo -from cubicweb import CW_SOFTWARE_ROOT, typed_eid +from cubicweb import CW_SOFTWARE_ROOT from cubicweb.schema import (CONSTRAINTS, ETYPE_NAME_MAP, VIRTUAL_RTYPES, PURE_VIRTUAL_RTYPES) from cubicweb.server import sqlutils @@ -58,7 +58,7 @@ if not value: continue try: - eid = typed_eid(value) + eid = int(value) except ValueError: print 'eid should be an integer' continue diff -r c4aa23af0baa -r 064231ce93d5 server/serverctl.py --- a/server/serverctl.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/serverctl.py Thu Mar 28 12:41:14 2013 +0100 @@ -39,6 +39,7 @@ from cubicweb.server.serverconfig import ( USER_OPTIONS, ServerConfiguration, SourceConfiguration, ask_source_config, generate_source_config) +from yams.diff import schema_diff # utility functions ########################################################### @@ -1065,12 +1066,31 @@ if val: print key, ':', val +class SchemaDiffCommand(Command): + """ generate a diff between schema and fsschema description + + the name of a diff tool to compare the two generated file + + """ + name = 'schema-diff' + arguments = ' ' + min_args = max_args = 2 + + def run(self, args): + appid = args.pop(0) + diff_tool = args.pop(0) + config = ServerConfiguration.config_for(appid) + repo, cnx = repo_cnx(config) + session = repo._get_session(cnx.sessionid, setcnxset=True) + fsschema = config.load_schema(expand_cubes=True) + schema_diff(repo.schema, fsschema, diff_tool) + for cmdclass in (CreateInstanceDBCommand, InitInstanceCommand, GrantUserOnInstanceCommand, ResetAdminPasswordCommand, StartRepositoryCommand, DBDumpCommand, DBRestoreCommand, DBCopyCommand, AddSourceCommand, CheckRepositoryCommand, RebuildFTICommand, - SynchronizeInstanceSchemaCommand, SynchronizeSourceCommand + SynchronizeInstanceSchemaCommand, SynchronizeSourceCommand, SchemaDiffCommand, ): CWCTL.register(cmdclass) diff -r c4aa23af0baa -r 064231ce93d5 server/session.py --- a/server/session.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/session.py Thu Mar 28 12:41:14 2013 +0100 @@ -48,28 +48,28 @@ @objectify_predicate def is_user_session(cls, req, **kwargs): - """repository side only predicate returning 1 if the session is a regular - user session and not an internal session - """ + """return 1 when session is not internal. + + This predicate can only be used repository side only. """ return not req.is_internal_session @objectify_predicate def is_internal_session(cls, req, **kwargs): - """repository side only predicate returning 1 if the session is not a regular - user session but an internal session - """ + """return 1 when session is not internal. + + This predicate can only be used repository side only. """ return req.is_internal_session @objectify_predicate def repairing(cls, req, **kwargs): - """repository side only predicate returning 1 if the session is not a regular - user session but an internal session - """ + """return 1 when repository is running in repair mode""" return req.vreg.config.repairing class transaction(object): - """context manager to enter a transaction for a session: when exiting the + """Ensure that the transaction is either commited or rollbacked at exit + + Context manager to enter a transaction for a session: when exiting the `with` block on exception, call `session.rollback()`, else call `session.commit()` on normal exit """ @@ -111,45 +111,533 @@ methods. """ def __init__(self, session, mode, *categories): + assert mode in (HOOKS_ALLOW_ALL, HOOKS_DENY_ALL) self.session = session + self.tx = session._tx self.mode = mode self.categories = categories + self.oldmode = None + self.changes = () def __enter__(self): - self.oldmode, self.changes = self.session.init_hooks_mode_categories( - self.mode, self.categories) + self.oldmode = self.tx.hooks_mode + self.tx.hooks_mode = self.mode + if self.mode is HOOKS_DENY_ALL: + self.changes = self.tx.enable_hook_categories(*self.categories) + else: + self.changes = self.tx.disable_hook_categories(*self.categories) + self.tx.ctx_count += 1 def __exit__(self, exctype, exc, traceback): - self.session.reset_hooks_mode_categories(self.oldmode, self.mode, self.changes) + self.tx.ctx_count -= 1 + if self.tx.ctx_count == 0: + self.session._clear_thread_storage(self.tx) + else: + try: + if self.categories: + if self.mode is HOOKS_DENY_ALL: + self.tx.disable_hook_categories(*self.categories) + else: + self.tx.enable_hook_categories(*self.categories) + finally: + self.tx.hooks_mode = self.oldmode class security_enabled(object): - """context manager to control security w/ session.execute, since by - default security is disabled on queries executed on the repository + """context manager to control security w/ session.execute, + + By default security is disabled on queries executed on the repository side. """ def __init__(self, session, read=None, write=None): self.session = session + self.tx = session._tx self.read = read self.write = write + self.oldread = None + self.oldwrite = None def __enter__(self): - self.oldread, self.oldwrite = self.session.init_security( - self.read, self.write) + if self.read is None: + self.oldread = None + else: + self.oldread = self.tx.read_security + self.tx.read_security = self.read + if self.write is None: + self.oldwrite = None + else: + self.oldwrite = self.tx.write_security + self.tx.write_security = self.write + self.tx.ctx_count += 1 def __exit__(self, exctype, exc, traceback): - self.session.reset_security(self.oldread, self.oldwrite) + self.tx.ctx_count -= 1 + if self.tx.ctx_count == 0: + self.session._clear_thread_storage(self.tx) + else: + if self.oldread is not None: + self.tx.read_security = self.oldread + if self.oldwrite is not None: + self.tx.write_security = self.oldwrite + +HOOKS_ALLOW_ALL = object() +HOOKS_DENY_ALL = object() +DEFAULT_SECURITY = object() # evaluated to true by design + +class SessionClosedError(RuntimeError): + pass + +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. + + Session object 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. + + .. automethod:: cubicweb.server.session.CnxSetTracker.record + .. automethod:: cubicweb.server.session.CnxSetTracker.forget + + Session use the :meth:`close` and :meth:`wait` method when closing. + + .. automethod:: cubicweb.server.session.CnxSetTracker.close + .. automethod:: cubicweb.server.session.CnxSetTracker.wait + + This object itself is threadsafe. It also requires caller to acquired its + lock in some situation. + """ + + def __init__(self): + self._active = True + self._condition = threading.Condition() + self._record = {} + + def __enter__(self): + self._condition.__enter__() + + def __exit__(self, *args): + self._condition.__exit__(*args) + + def record(self, txid, cnxset): + """Inform the tracker that a txid have acquired a cnxset + + This methode is to be used by Transaction object. + + This method fails when: + - The txid already have a recorded cnxset. + - The tracker is not active anymore. + + Notes about the caller: + (1) It is responsible for retrieving a cnxset. + (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 + retrieved the cnxset to prevent deadlock. + + A typical usage look like:: + + cnxset = repo._get_cnxset() # (1) + try: + with cnxset_tracker: # (3) and (4) + cnxset_tracker.record(caller.id, cnxset) + # (3') operation ends when caller is in expected state only + caller.cnxset = cnxset + except Exception: + repo._free_cnxset(cnxset) # (2) + raise + """ + # dubious since the caller is suppose 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)' + % (txid, old)) + self._record[txid] = cnxset + + def forget(self, txid, cnxset): + """Inform the tracker that a txid have release a cnxset + + This methode is to be used by Transaction object. + + This method fails when: + - The cnxset for the txid does not match the recorded one. + + Notes about the caller: + (1) It is responsible for releasing the cnxset. + (2) It should acquire the tracker lock during the operation to ensure + the internal tracker state is always accurate regarding its own state. + + A typical usage look like:: + + cnxset = caller.cnxset + try: + with cnxset_tracker: + # (2) you can not have caller.cnxset out of sync with + # cnxset_tracker state while unlocked + caller.cnxset = None + cnxset_tracker.forget(caller.id, cnxset) + finally: + cnxset = repo._free_cnxset(cnxset) # (1) + """ + with self._condition: + old = self._record.get(txid, None) + if old is not cnxset: + raise ValueError('recorded cnxset for "%s" mismatch: %r != %r' + % (txid, old, cnxset)) + self._record.pop(txid) + self._condition.notify_all() + + def close(self): + """Marks the tracker as inactive. + + This methode is to be used by Session object. + + Inactive tracker does not accept new record anymore. + """ + with self._condition: + self._active = False + + def wait(self, timeout=10): + """Wait for all recorded cnxset to be released + + This methode is to be used by Session object. + + returns a tuple of transaction id that remains open. + """ + with self._condition: + if self._active: + raise RuntimeError('Cannot wait on active tracker.' + ' Call tracker.close() first') + while self._record and timeout > 0: + start = time() + self._condition.wait(timeout) + timeout -= time() - start + return tuple(self._record) + +class Transaction(object): + """Repository Transaction + + Holds all transaction related data + + Database connections resource: + + :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 + + :attr:`mode`, string telling the connections set handling mode, may be one + of 'read' (connections set may be freed), 'write' (some write was done in + the connections set, it can't be freed before end of the transaction), + 'transaction' (we want to keep the connections set during all the + transaction, with or without writing) + + Internal transaction 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. + + :attr:`pending_operations`, ordered list of operations to be processed on + commit/rollback + + :attr:`commit_state`, describing the transaction commit state, may be one + 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). + + Hooks controls: + + :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`. + + :attr:`enabled_hook_cats`, when :attr:`hooks_mode` is + `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled. + + :attr:`disabled_hook_cats`, when :attr:`hooks_mode` is + `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled. + + Security level Management: + + :attr:`read_security` and :attr:`write_security`, boolean flags telling if + read/write security is currently activated. + + """ + + def __init__(self, txid, session, rewriter): + #: transaction unique id + self.transactionid = txid + #: reentrance handling + self.ctx_count = 0 + + #: connection handling mode + self.mode = session.default_mode + #: connection set used to execute queries on sources + self._cnxset = None + #: CnxSetTracker used to report cnxset usage + self._cnxset_tracker = session._cnxset_tracker + #: is this transaction from a client or internal to the repo + self.running_dbapi_query = True + + #: dict containing arbitrary data cleared at the end of the transaction + self.data = {} + #: ordered list of operations to be processed on commit/rollback + self.pending_operations = [] + #: (None, 'precommit', 'postcommit', 'uncommitable') + self.commit_state = None + + ### hook control attribute + self.hooks_mode = HOOKS_ALLOW_ALL + self.disabled_hook_cats = set() + self.enabled_hook_cats = set() + self.pruned_hooks_cache = {} -class TransactionData(object): - def __init__(self, txid): - self.transactionid = txid - self.ctx_count = 0 + ### security control attributes + self._read_security = DEFAULT_SECURITY # handled by a property + self.write_security = DEFAULT_SECURITY + + # undo control + config = session.repo.config + if config.creating or config.repairing or session.is_internal_session: + self.undo_actions = False + else: + self.undo_actions = config['undo-enabled'] + + # RQLRewriter are not thread safe + self._rewriter = rewriter + + @property + def transaction_data(self): + return self.data + + + def clear(self): + """reset internal data""" + self.data = {} + #: ordered list of operations to be processed on commit/rollback + self.pending_operations = [] + #: (None, 'precommit', 'postcommit', 'uncommitable') + self.commit_state = None + self.pruned_hooks_cache = {} + # Connection Set Management ############################################### + @property + def cnxset(self): + return self._cnxset + + @cnxset.setter + def cnxset(self, new_cnxset): + with self._cnxset_tracker: + old_cnxset = self._cnxset + if new_cnxset is old_cnxset: + return #nothing to do + if old_cnxset is not None: + self._cnxset = None + self.ctx_count -= 1 + self._cnxset_tracker.forget(self.transactionid, old_cnxset) + if new_cnxset is not None: + self._cnxset_tracker.record(self.transactionid, new_cnxset) + self._cnxset = new_cnxset + self.ctx_count += 1 + + # Entity cache management ################################################# + # + # The transaction entity cache as held in tx.data it is removed at end the + # end of the transaction (commit and rollback) + # + # XXX transaction level caching may be a pb with multiple repository + # instances, but 1. this is probably not the only one :$ and 2. it may be + # an acceptable risk. Anyway we could activate it or not according to a + # configuration option + + def set_entity_cache(self, entity): + """Add `entity` to the transaction entity cache""" + ecache = self.data.setdefault('ecache', {}) + ecache.setdefault(entity.eid, entity) + + def entity_cache(self, eid): + """get cache entity for `eid`""" + return self.data['ecache'][eid] + + def cached_entities(self): + """return the whole entity cache""" + return self.data.get('ecache', {}).values() + + def drop_entity_cache(self, eid=None): + """drop entity from the cache + + If eid is None, the whole cache is dropped""" + if eid is None: + self.data.pop('ecache', None) + 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. + + def deleted_in_transaction(self, eid): + """return True if the entity of the given eid is being deleted in the + current transaction + """ + return eid in self.data.get('pendingeids', ()) + + def added_in_transaction(self, eid): + """return True if the entity of the given eid is being created in the + current transaction + """ + return eid in self.data.get('neweids', ()) + + # Operation management #################################################### + + def add_operation(self, operation, index=None): + """add an operation to be executed at the end of the transaction""" + if index is None: + self.pending_operations.append(operation) + else: + self.pending_operations.insert(index, operation) + + # Hooks control ########################################################### + + def disable_hook_categories(self, *categories): + """disable the given hook categories: + + - on HOOKS_DENY_ALL mode, ensure those categories are not enabled + - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled + """ + changes = set() + self.pruned_hooks_cache.clear() + categories = set(categories) + if self.hooks_mode is HOOKS_DENY_ALL: + enabledcats = self.enabled_hook_cats + changes = enabledcats & categories + enabledcats -= changes # changes is small hence faster + else: + disabledcats = self.disabled_hook_cats + changes = categories - disabledcats + disabledcats |= changes # changes is small hence faster + return tuple(changes) + + def enable_hook_categories(self, *categories): + """enable the given hook categories: + + - on HOOKS_DENY_ALL mode, ensure those categories are enabled + - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled + """ + changes = set() + self.pruned_hooks_cache.clear() + categories = set(categories) + if self.hooks_mode is HOOKS_DENY_ALL: + enabledcats = self.enabled_hook_cats + changes = categories - enabledcats + enabledcats |= changes # changes is small hence faster + else: + disabledcats = self.disabled_hook_cats + changes = disabledcats & categories + disabledcats -= changes # changes is small hence faster + return tuple(changes) + + def is_hook_category_activated(self, category): + """return a boolean telling if the given category is currently activated + or not + """ + if self.hooks_mode is HOOKS_DENY_ALL: + return category in self.enabled_hook_cats + return category not in self.disabled_hook_cats + + def is_hook_activated(self, hook): + """return a boolean telling if the given hook class is currently + activated or not + """ + return self.is_hook_category_activated(hook.category) + + # Security management ##################################################### + @property + def read_security(self): + return self._read_security + + @read_security.setter + def read_security(self, activated): + oldmode = self._read_security + self._read_security = activated + # running_dbapi_query used to detect hooks triggered by a 'dbapi' query + # (eg not issued on the session). This is tricky since we the execution + # model of a (write) user query is: + # + # repository.execute (security enabled) + # \-> querier.execute + # \-> repo.glob_xxx (add/update/delete entity/relation) + # \-> deactivate security before calling hooks + # \-> WE WANT TO CHECK QUERY NATURE HERE + # \-> potentially, other calls to querier.execute + # + # so we can't rely on simply checking session.read_security, but + # recalling the first transition from DEFAULT_SECURITY to something + # else (False actually) is not perfect but should be enough + # + # also reset running_dbapi_query to true when we go back to + # DEFAULT_SECURITY + self.running_dbapi_query = (oldmode is DEFAULT_SECURITY + or activated is DEFAULT_SECURITY) + + # undo support ############################################################ + + def ertype_supports_undo(self, ertype): + return self.undo_actions and ertype not in NO_UNDO_TYPES + + def transaction_uuid(self, set=True): + uuid = self.data.get('tx_uuid') + if set and uuid is None: + raise KeyError + return uuid + + def transaction_inc_action_counter(self): + num = self.data.setdefault('tx_action_count', 0) + 1 + self.data['tx_action_count'] = num + return num + + +def tx_attr(attr_name, writable=False): + """return a property to forward attribute access to transaction. + + This is to be used by session""" + args = {} + def attr_from_tx(session): + return getattr(session._tx, attr_name) + args['fget'] = attr_from_tx + if writable: + def write_attr(session, value): + return setattr(session._tx, attr_name, value) + args['fset'] = write_attr + return property(**args) + +def tx_meth(meth_name): + """return a function forwarding calls to transaction. + + This is to be used by session""" + def meth_from_tx(session, *args, **kwargs): + return getattr(session._tx, meth_name)(*args, **kwargs) + return meth_from_tx class Session(RequestSessionBase): - """Repository usersession, tie a session id, user, connections set and - other session data all together. + """Repository user session + + This tie all together: + * session id, + * user, + * connections set, + * other session data. About session storage / transactions ------------------------------------ @@ -161,20 +649,17 @@ :attr:`data` is a dictionary containing shared data, used to communicate extra information between the client and the repository - :attr:`_tx_data` is a dictionary of :class:`TransactionData` instance, one + :attr:`_txs` is a dictionary of :class:`TransactionData` instance, one for each running transaction. The key is the transaction id. By default the transaction id is the thread name but it can be otherwise (per dbapi cursor for instance, or per thread name *from another process*). - :attr:`__threaddata` is a thread local storage whose `txdata` attribute - refers to the proper instance of :class:`TransactionData` according to the + :attr:`__threaddata` is a thread local storage whose `tx` attribute + refers to the proper instance of :class:`Transaction` according to the transaction. - :attr:`_threads_in_transaction` is a set of (thread, connections set) - referencing threads that currently hold a connections set for the session. - - You should not have to use neither :attr:`_txdata` nor :attr:`__threaddata`, - simply access transaction data transparently through the :attr:`_threaddata` + You should not have to use neither :attr:`_tx` nor :attr:`__threaddata`, + simply access transaction data transparently through the :attr:`_tx` property. Also, you usually don't have to access it directly since current transaction's data may be accessed/modified through properties / methods: @@ -184,11 +669,24 @@ this may also be used as a communication channel between the client and the repository. + .. automethod:: cubicweb.server.session.Session.get_shared_data + .. automethod:: cubicweb.server.session.Session.set_shared_data + .. automethod:: cubicweb.server.session.Session.added_in_transaction + .. automethod:: cubicweb.server.session.Session.deleted_in_transaction + + Transaction state information: + + :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. During a transaction, the connection set may be freed so that is may be used by another session as long as no writing is done. This means we can have multiple sessions with a reasonably low connections set pool size. + .. automethod:: cubicweb.server.session.set_cnxset + .. automethod:: cubicweb.server.session.free_cnxset + :attr:`mode`, string telling the connections set handling mode, may be one of 'read' (connections set may be freed), 'write' (some write was done in the connections set, it can't be freed before end of the transaction), @@ -204,9 +702,20 @@ 'uncommitable' (some :exc:`ValidationError` or :exc:`Unauthorized` error has been raised during the transaction and so it must be rollbacked). + .. automethod:: cubicweb.server.session.Session.commit + .. automethod:: cubicweb.server.session.Session.rollback + .. automethod:: cubicweb.server.session.Session.close + .. automethod:: cubicweb.server.session.Session.closed + + Security level Management: + :attr:`read_security` and :attr:`write_security`, boolean flags telling if read/write security is currently activated. + .. automethod:: cubicweb.server.session.Session.security_enabled + + Hooks Management: + :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`. :attr:`enabled_hook_categories`, when :attr:`hooks_mode` is @@ -215,12 +724,23 @@ :attr:`disabled_hook_categories`, when :attr:`hooks_mode` is `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled. + .. automethod:: cubicweb.server.session.Session.deny_all_hooks_but + .. automethod:: cubicweb.server.session.Session.allow_all_hooks_but + .. automethod:: cubicweb.server.session.Session.is_hook_category_activated + .. automethod:: cubicweb.server.session.Session.is_hook_activated - :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 + Data manipulation: + + .. automethod:: cubicweb.server.session.Session.add_relation + .. automethod:: cubicweb.server.session.Session.add_relations + .. automethod:: cubicweb.server.session.Session.delete_relation - .. automethod:: cubicweb.server.session.deny_all_hooks_but - .. automethod:: cubicweb.server.session.all_all_hooks_but + Other: + + .. automethod:: cubicweb.server.session.Session.call_service + + + """ is_request = False is_internal_session = False @@ -232,11 +752,6 @@ self.repo = repo self.timestamp = time() self.default_mode = 'read' - # undo support - if repo.config.creating or repo.config.repairing or self.is_internal_session: - self.undo_actions = False - else: - self.undo_actions = repo.config['undo-enabled'] # short cut to querier .execute method self._execute = repo.querier.execute # shared data, used to communicate extra information between the client @@ -244,17 +759,52 @@ self.data = {} # i18n initialization self.set_language(user.prefered_language()) - # internals - self._tx_data = {} + ### internals + # Transaction of this section + self._txs = {} + # Data local to the thread self.__threaddata = threading.local() - self._threads_in_transaction = set() + self._cnxset_tracker = CnxSetTracker() self._closed = False - self._closed_lock = threading.Lock() + self._lock = threading.RLock() def __unicode__(self): return '' % ( unicode(self.user.login), self.id, id(self)) + def get_tx(self, txid): + """return the transaction attached to this session + + Transaction is created if necessary""" + with self._lock: # no transaction exist with the same id + try: + tx = self._txs[txid] + except KeyError: + rewriter = RQLRewriter(self) + tx = Transaction(txid, self, rewriter) + self._txs[txid] = tx + return tx + + def set_tx(self, txid=None): + """set the default transaction of the current thread to + + Transaction is created if necessary""" + if txid is None: + txid = threading.currentThread().getName() + self.__threaddata.tx = self.get_tx(txid) + + @property + def _tx(self): + """default transaction for current session in current thread""" + try: + return self.__threaddata.tx + except AttributeError: + self.set_tx() + return self.__threaddata.tx + + def get_option_value(self, option, foreid=None): + return self.repo.get_option_value(option, foreid) + def transaction(self, free_cnxset=True): """return context manager to enter a transaction for the session: when exiting the `with` block on exception, call `session.rollback()`, else @@ -265,40 +815,19 @@ """ return transaction(self, free_cnxset) - def set_tx_data(self, txid=None): - if txid is None: - txid = threading.currentThread().getName() - try: - self.__threaddata.txdata = self._tx_data[txid] - except KeyError: - self.__threaddata.txdata = self._tx_data[txid] = TransactionData(txid) - - @property - def _threaddata(self): - try: - return self.__threaddata.txdata - except AttributeError: - self.set_tx_data() - return self.__threaddata.txdata - - def get_option_value(self, option, foreid=None): - return self.repo.get_option_value(option, foreid) def hijack_user(self, user): """return a fake request/session using specified user""" session = Session(user, self.repo) - threaddata = session._threaddata - threaddata.cnxset = self.cnxset - # we attributed a connections set, need to update ctx_count else it will be freed - # while undesired - threaddata.ctx_count = 1 + tx = session._tx + tx.cnxset = self.cnxset # share pending_operations, else operation added in the hi-jacked # session such as SendMailOp won't ever be processed - threaddata.pending_operations = self.pending_operations - # everything in transaction_data should be copied back but the entity + tx.pending_operations = self.pending_operations + # everything in tx.data should be copied back but the entity # type cache we don't want to avoid security pb - threaddata.transaction_data = self.transaction_data.copy() - threaddata.transaction_data.pop('ecache', None) + tx.data = self._tx.data.copy() + tx.data.pop('ecache', None) return session def add_relation(self, fromeid, rtype, toeid): @@ -443,17 +972,8 @@ self.cnxset.reconnect(source) return source.doexec(self, sql, args, rollback=rollback_on_failure) - def deleted_in_transaction(self, eid): - """return True if the entity of the given eid is being deleted in the - current transaction - """ - return eid in self.transaction_data.get('pendingeids', ()) - - def added_in_transaction(self, eid): - """return True if the entity of the given eid is being created in the - current transaction - """ - return eid in self.transaction_data.get('neweids', ()) + deleted_in_transaction = tx_meth('deleted_in_transaction') + added_in_transaction = tx_meth('added_in_transaction') def rtype_eids_rdef(self, rtype, eidfrom, eidto): # use type_and_source_from_eid instead of type_from_eid for optimization @@ -464,231 +984,30 @@ # security control ######################################################### - DEFAULT_SECURITY = object() # evaluated to true by design def security_enabled(self, read=None, write=None): return security_enabled(self, read=read, write=write) - def init_security(self, read, write): - if read is None: - oldread = None - else: - oldread = self.set_read_security(read) - if write is None: - oldwrite = None - else: - oldwrite = self.set_write_security(write) - self._threaddata.ctx_count += 1 - return oldread, oldwrite - - def reset_security(self, read, write): - txstore = self._threaddata - txstore.ctx_count -= 1 - if txstore.ctx_count == 0: - self._clear_thread_storage(txstore) - else: - if read is not None: - self.set_read_security(read) - if write is not None: - self.set_write_security(write) - - @property - def read_security(self): - """return a boolean telling if read security is activated or not""" - txstore = self._threaddata - if txstore is None: - return self.DEFAULT_SECURITY - try: - return txstore.read_security - except AttributeError: - txstore.read_security = self.DEFAULT_SECURITY - return txstore.read_security - - def set_read_security(self, activated): - """[de]activate read security, returning the previous value set for - later restoration. - - you should usually use the `security_enabled` context manager instead - of this to change security settings. - """ - txstore = self._threaddata - if txstore is None: - return self.DEFAULT_SECURITY - oldmode = getattr(txstore, 'read_security', self.DEFAULT_SECURITY) - txstore.read_security = activated - # dbapi_query used to detect hooks triggered by a 'dbapi' query (eg not - # issued on the session). This is tricky since we the execution model of - # a (write) user query is: - # - # repository.execute (security enabled) - # \-> querier.execute - # \-> repo.glob_xxx (add/update/delete entity/relation) - # \-> deactivate security before calling hooks - # \-> WE WANT TO CHECK QUERY NATURE HERE - # \-> potentially, other calls to querier.execute - # - # so we can't rely on simply checking session.read_security, but - # recalling the first transition from DEFAULT_SECURITY to something - # else (False actually) is not perfect but should be enough - # - # also reset dbapi_query to true when we go back to DEFAULT_SECURITY - txstore.dbapi_query = (oldmode is self.DEFAULT_SECURITY - or activated is self.DEFAULT_SECURITY) - return oldmode - - @property - def write_security(self): - """return a boolean telling if write security is activated or not""" - txstore = self._threaddata - if txstore is None: - return self.DEFAULT_SECURITY - try: - return txstore.write_security - except AttributeError: - txstore.write_security = self.DEFAULT_SECURITY - return txstore.write_security - - def set_write_security(self, activated): - """[de]activate write security, returning the previous value set for - later restoration. - - you should usually use the `security_enabled` context manager instead - of this to change security settings. - """ - txstore = self._threaddata - if txstore is None: - return self.DEFAULT_SECURITY - oldmode = getattr(txstore, 'write_security', self.DEFAULT_SECURITY) - txstore.write_security = activated - return oldmode - - @property - def running_dbapi_query(self): - """return a boolean telling if it's triggered by a db-api query or by - a session query. - - To be used in hooks, else may have a wrong value. - """ - return getattr(self._threaddata, 'dbapi_query', True) + read_security = tx_attr('read_security', writable=True) + write_security = tx_attr('write_security', writable=True) + running_dbapi_query = tx_attr('running_dbapi_query') # hooks activation control ################################################# # all hooks should be activated during normal execution - HOOKS_ALLOW_ALL = object() - HOOKS_DENY_ALL = object() - def allow_all_hooks_but(self, *categories): - return hooks_control(self, self.HOOKS_ALLOW_ALL, *categories) + return hooks_control(self, HOOKS_ALLOW_ALL, *categories) def deny_all_hooks_but(self, *categories): - return hooks_control(self, self.HOOKS_DENY_ALL, *categories) - - @property - def hooks_mode(self): - return getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL) - - def set_hooks_mode(self, mode): - assert mode is self.HOOKS_ALLOW_ALL or mode is self.HOOKS_DENY_ALL - oldmode = getattr(self._threaddata, 'hooks_mode', self.HOOKS_ALLOW_ALL) - self._threaddata.hooks_mode = mode - return oldmode - - def init_hooks_mode_categories(self, mode, categories): - oldmode = self.set_hooks_mode(mode) - if mode is self.HOOKS_DENY_ALL: - changes = self.enable_hook_categories(*categories) - else: - changes = self.disable_hook_categories(*categories) - self._threaddata.ctx_count += 1 - return oldmode, changes + return hooks_control(self, HOOKS_DENY_ALL, *categories) - def reset_hooks_mode_categories(self, oldmode, mode, categories): - txstore = self._threaddata - txstore.ctx_count -= 1 - if txstore.ctx_count == 0: - self._clear_thread_storage(txstore) - else: - try: - if categories: - if mode is self.HOOKS_DENY_ALL: - return self.disable_hook_categories(*categories) - else: - return self.enable_hook_categories(*categories) - finally: - self.set_hooks_mode(oldmode) - - @property - def disabled_hook_categories(self): - try: - return getattr(self._threaddata, 'disabled_hook_cats') - except AttributeError: - cats = self._threaddata.disabled_hook_cats = set() - return cats - - @property - def enabled_hook_categories(self): - try: - return getattr(self._threaddata, 'enabled_hook_cats') - except AttributeError: - cats = self._threaddata.enabled_hook_cats = set() - return cats + hooks_mode = tx_attr('hooks_mode') - def disable_hook_categories(self, *categories): - """disable the given hook categories: - - - on HOOKS_DENY_ALL mode, ensure those categories are not enabled - - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled - """ - changes = set() - self.pruned_hooks_cache.clear() - if self.hooks_mode is self.HOOKS_DENY_ALL: - enabledcats = self.enabled_hook_categories - for category in categories: - if category in enabledcats: - enabledcats.remove(category) - changes.add(category) - else: - disabledcats = self.disabled_hook_categories - for category in categories: - if category not in disabledcats: - disabledcats.add(category) - changes.add(category) - return tuple(changes) - - def enable_hook_categories(self, *categories): - """enable the given hook categories: - - - on HOOKS_DENY_ALL mode, ensure those categories are enabled - - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled - """ - changes = set() - self.pruned_hooks_cache.clear() - if self.hooks_mode is self.HOOKS_DENY_ALL: - enabledcats = self.enabled_hook_categories - for category in categories: - if category not in enabledcats: - enabledcats.add(category) - changes.add(category) - else: - disabledcats = self.disabled_hook_categories - for category in categories: - if category in disabledcats: - disabledcats.remove(category) - changes.add(category) - return tuple(changes) - - def is_hook_category_activated(self, category): - """return a boolean telling if the given category is currently activated - or not - """ - if self.hooks_mode is self.HOOKS_DENY_ALL: - return category in self.enabled_hook_categories - return category not in self.disabled_hook_categories - - def is_hook_activated(self, hook): - """return a boolean telling if the given hook class is currently - activated or not - """ - return self.is_hook_category_activated(hook.category) + disabled_hook_categories = tx_attr('disabled_hook_cats') + enabled_hook_categories = tx_attr('enabled_hook_cats') + disable_hook_categories = tx_meth('disable_hook_categories') + enable_hook_categories = tx_meth('enable_hook_categories') + is_hook_category_activated = tx_meth('is_hook_category_activated') + is_hook_activated = tx_meth('is_hook_activated') # connection management ################################################### @@ -712,19 +1031,8 @@ else: # mode == 'write' self.default_mode = 'read' - def get_mode(self): - return getattr(self._threaddata, 'mode', self.default_mode) - def set_mode(self, value): - self._threaddata.mode = value - mode = property(get_mode, set_mode, - doc='transaction mode (read/write/transaction), resetted to' - ' default_mode on commit / rollback') - - def get_commit_state(self): - return getattr(self._threaddata, 'commit_state', None) - def set_commit_state(self, value): - self._threaddata.commit_state = value - commit_state = property(get_commit_state, set_commit_state) + mode = tx_attr('mode', writable=True) + commit_state = tx_attr('commit_state', writable=True) @property def cnxset(self): @@ -732,65 +1040,53 @@ if self._closed: self.free_cnxset(True) raise Exception('try to access connections set on a closed session %s' % self.id) - return getattr(self._threaddata, 'cnxset', None) + return self._tx.cnxset def set_cnxset(self): """the session need a connections set to execute some queries""" - with self._closed_lock: + with self._lock: # can probably be removed if self._closed: self.free_cnxset(True) raise Exception('try to set connections set on a closed session %s' % self.id) - if self.cnxset is None: - # get connections set first to avoid race-condition - self._threaddata.cnxset = cnxset = self.repo._get_cnxset() - self._threaddata.ctx_count += 1 + tx = self._tx + if tx.cnxset is None: + cnxset = self.repo._get_cnxset() try: - cnxset.cnxset_set() - except Exception: - self._threaddata.cnxset = None + self._tx.cnxset = cnxset + try: + cnxset.cnxset_set() + except: + self._tx.cnxset = None + raise + except: self.repo._free_cnxset(cnxset) raise - self._threads_in_transaction.add( - (threading.currentThread(), cnxset) ) - return self._threaddata.cnxset - - def _free_thread_cnxset(self, thread, cnxset, force_close=False): - try: - self._threads_in_transaction.remove( (thread, cnxset) ) - except KeyError: - # race condition on cnxset freeing (freed by commit or rollback vs - # close) - pass - else: - if force_close: - cnxset.reconnect() - else: - cnxset.cnxset_freed() - # free cnxset once everything is done to avoid race-condition - self.repo._free_cnxset(cnxset) + return tx.cnxset def free_cnxset(self, ignoremode=False): """the session is no longer using its connections set, at least for some time""" # cnxset may be none if no operation has been done since last commit # or rollback - cnxset = getattr(self._threaddata, 'cnxset', None) + tx = self._tx + cnxset = tx.cnxset if cnxset is not None and (ignoremode or self.mode == 'read'): - # even in read mode, we must release the current transaction - self._free_thread_cnxset(threading.currentThread(), cnxset) - del self._threaddata.cnxset - self._threaddata.ctx_count -= 1 + try: + tx.cnxset = None + finally: + cnxset.cnxset_freed() + self.repo._free_cnxset(cnxset) def _touch(self): """update latest session usage timestamp and reset mode to read""" self.timestamp = time() - self.local_perm_cache.clear() # XXX simply move in transaction_data, no? + self.local_perm_cache.clear() # XXX simply move in tx.data, no? # shared data handling ################################################### def get_shared_data(self, key, default=None, pop=False, txdata=False): """return value associated to `key` in session data""" if txdata: - data = self.transaction_data + data = self._tx.data else: data = self.data if pop: @@ -801,7 +1097,7 @@ def set_shared_data(self, key, value, txdata=False): """set value associated to `key` in session data""" if txdata: - self.transaction_data[key] = value + self._tx.data[key] = value else: self.data[key] = value @@ -818,28 +1114,10 @@ """return a rql cursor""" return self - def set_entity_cache(self, entity): - # XXX session level caching may be a pb with multiple repository - # instances, but 1. this is probably not the only one :$ and 2. it - # may be an acceptable risk. Anyway we could activate it or not - # according to a configuration option - try: - self.transaction_data['ecache'].setdefault(entity.eid, entity) - except KeyError: - self.transaction_data['ecache'] = ecache = {} - ecache[entity.eid] = entity - - def entity_cache(self, eid): - return self.transaction_data['ecache'][eid] - - def cached_entities(self): - return self.transaction_data.get('ecache', {}).values() - - def drop_entity_cache(self, eid=None): - if eid is None: - self.transaction_data.pop('ecache', None) - else: - del self.transaction_data['ecache'][eid] + set_entity_cache = tx_meth('set_entity_cache') + entity_cache = tx_meth('entity_cache') + cache_entities = tx_meth('cached_entities') + drop_entity_cache = tx_meth('drop_entity_cache') def from_controller(self): """return the id (string) of the controller issuing the request (no @@ -883,34 +1161,29 @@ by _touch """ try: - txstore = self.__threaddata.txdata + tx = self.__threaddata.tx except AttributeError: pass else: if free_cnxset: self.free_cnxset() - if txstore.ctx_count == 0: - self._clear_thread_storage(txstore) + if tx.ctx_count == 0: + self._clear_thread_storage(tx) else: - self._clear_tx_storage(txstore) + self._clear_tx_storage(tx) else: - self._clear_tx_storage(txstore) + self._clear_tx_storage(tx) - def _clear_thread_storage(self, txstore): - self._tx_data.pop(txstore.transactionid, None) + def _clear_thread_storage(self, tx): + self._txs.pop(tx.transactionid, None) try: - del self.__threaddata.txdata + del self.__threaddata.tx except AttributeError: pass - def _clear_tx_storage(self, txstore): - for name in ('commit_state', 'transaction_data', - 'pending_operations', '_rewriter', - 'pruned_hooks_cache'): - try: - delattr(txstore, name) - except AttributeError: - continue + def _clear_tx_storage(self, tx): + tx.clear() + tx._rewriter = RQLRewriter(self) def commit(self, free_cnxset=True, reset_pool=None): """commit the current session's transaction""" @@ -1007,7 +1280,7 @@ DeprecationWarning, stacklevel=2) free_cnxset = reset_pool # don't use self.cnxset, rollback may be called with _closed == True - cnxset = getattr(self._threaddata, 'cnxset', None) + cnxset = self._tx.cnxset if cnxset is None: self._clear_thread_data() self._touch() @@ -1032,97 +1305,65 @@ self._clear_thread_data(free_cnxset) def close(self): - """do not close connections set on session close, since they are shared now""" - with self._closed_lock: + # do not close connections set on session close, since they are shared now + tracker = self._cnxset_tracker + with self._lock: self._closed = True - # copy since _threads_in_transaction maybe modified while waiting - for thread, cnxset in self._threads_in_transaction.copy(): - if thread is threading.currentThread(): - continue - self.info('waiting for thread %s', thread) - # do this loop/break instead of a simple join(10) in case thread is - # the main thread (in which case it will be removed from - # self._threads_in_transaction but still be alive...) - for i in xrange(10): - thread.join(1) - if not (thread.isAlive() and - (thread, cnxset) in self._threads_in_transaction): - break - else: - self.error('thread %s still alive after 10 seconds, will close ' - 'session anyway', thread) - self._free_thread_cnxset(thread, cnxset, force_close=True) + tracker.close() self.rollback() + self.info('waiting for open transaction of session: %s', self) + timeout = 10 + pendings = tracker.wait(timeout) + if pendings: + self.error('%i transaction still alive after 10 seconds, will close ' + 'session anyway', len(pendings)) + for txid in pendings: + tx = self._txs.get(txid) + if tx is not None: + # drop tx.cnxset + with tracker: + try: + cnxset = tx.cnxset + if cnxset is None: + continue + tx.cnxset = None + except RuntimeError: + msg = 'issue while force free of cnxset in %s' + self.error(msg, tx) + # cnxset.reconnect() do an hard reset of the cnxset + # it force it to be freed + cnxset.reconnect() + self.repo._free_cnxset(cnxset) del self.__threaddata - del self._tx_data + del self._txs @property def closed(self): - return not hasattr(self, '_tx_data') + return not hasattr(self, '_txs') # transaction data/operations management ################################## - @property - def transaction_data(self): - try: - return self._threaddata.transaction_data - except AttributeError: - self._threaddata.transaction_data = {} - return self._threaddata.transaction_data - - @property - def pending_operations(self): - try: - return self._threaddata.pending_operations - except AttributeError: - self._threaddata.pending_operations = [] - return self._threaddata.pending_operations - - @property - def pruned_hooks_cache(self): - try: - return self._threaddata.pruned_hooks_cache - except AttributeError: - self._threaddata.pruned_hooks_cache = {} - return self._threaddata.pruned_hooks_cache - - def add_operation(self, operation, index=None): - """add an operation""" - if index is None: - self.pending_operations.append(operation) - else: - self.pending_operations.insert(index, operation) + transaction_data = tx_attr('data') + pending_operations = tx_attr('pending_operations') + pruned_hooks_cache = tx_attr('pruned_hooks_cache') + add_operation = tx_meth('add_operation') # undo support ############################################################ - def ertype_supports_undo(self, ertype): - return self.undo_actions and ertype not in NO_UNDO_TYPES + ertype_supports_undo = tx_meth('ertype_supports_undo') + transaction_inc_action_counter = tx_meth('transaction_inc_action_counter') def transaction_uuid(self, set=True): try: - return self.transaction_data['tx_uuid'] + return self._tx.transaction_uuid(set=set) except KeyError: - if not set: - return - self.transaction_data['tx_uuid'] = uuid = uuid4().hex + self._tx.data['tx_uuid'] = uuid = uuid4().hex self.repo.system_source.start_undoable_transaction(self, uuid) return uuid - def transaction_inc_action_counter(self): - num = self.transaction_data.setdefault('tx_action_count', 0) + 1 - self.transaction_data['tx_action_count'] = num - return num - # querier helpers ######################################################### - @property - def rql_rewriter(self): - # in thread local storage since the rewriter isn't thread safe - try: - return self._threaddata._rewriter - except AttributeError: - self._threaddata._rewriter = RQLRewriter(self) - return self._threaddata._rewriter + rql_rewriter = tx_attr('_rewriter') # deprecated ############################################################### @@ -1143,32 +1384,15 @@ def reset_pool(self): return self.free_cnxset() - @deprecated("[3.7] execute is now unsafe by default in hooks/operation. You" - " can also control security with the security_enabled context " - "manager") - def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True, - propagate=False): - """like .execute but with security checking disabled (this method is - internal to the server, it's not part of the db-api) - """ - with security_enabled(self, read=False, write=False): - return self.execute(rql, kwargs, eid_key, build_descr) - - @property - @deprecated("[3.7] is_super_session is deprecated, test " - "session.read_security and or session.write_security") - def is_super_session(self): - return not self.read_security or not self.write_security - - @deprecated("[3.7] session is actual session") - def actual_session(self): - """return the original parent session if any, else self""" - return self - # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None +Session.HOOKS_ALLOW_ALL = HOOKS_ALLOW_ALL +Session.HOOKS_DENY_ALL = HOOKS_DENY_ALL +Session.DEFAULT_SECURITY = DEFAULT_SECURITY + + class InternalSession(Session): """special session created internaly by the repository""" @@ -1194,7 +1418,7 @@ if self.repo.shutting_down: self.free_cnxset(True) raise ShuttingDown('repository is shutting down') - return getattr(self._threaddata, 'cnxset', None) + return self._tx.cnxset class InternalManager(object): diff -r c4aa23af0baa -r 064231ce93d5 server/ssplanner.py --- a/server/ssplanner.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/ssplanner.py Thu Mar 28 12:41:14 2013 +0100 @@ -22,7 +22,7 @@ from rql.stmts import Union, Select from rql.nodes import Constant, Relation -from cubicweb import QueryError, typed_eid +from cubicweb import QueryError from cubicweb.schema import VIRTUAL_RTYPES from cubicweb.rqlrewrite import add_types_restriction from cubicweb.server.edition import EditedEntity @@ -79,7 +79,7 @@ if rel.r_type == 'eid' and not rel.neged(strict=True): lhs, rhs = rel.get_variable_parts() if isinstance(rhs, Constant): - eid = typed_eid(rhs.eval(plan.args)) + eid = int(rhs.eval(plan.args)) # check read permission here since it may not be done by # the generated select substep if not emited (eg nothing # to be selected) @@ -516,7 +516,7 @@ """execute this step""" results = self.execute_child() if results: - todelete = frozenset(typed_eid(eid) for eid, in results) + todelete = frozenset(int(eid) for eid, in results) session = self.plan.session session.repo.glob_delete_entities(session, todelete) return results @@ -562,7 +562,7 @@ lhsval = _handle_relterm(lhsinfo, row, newrow) rhsval = _handle_relterm(rhsinfo, row, newrow) if rschema.final or rschema.inlined: - eid = typed_eid(lhsval) + eid = int(lhsval) try: edited = edefs[eid] except KeyError: diff -r c4aa23af0baa -r 064231ce93d5 server/test/unittest_session.py --- a/server/test/unittest_session.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/test/unittest_session.py Thu Mar 28 12:41:14 2013 +0100 @@ -32,7 +32,7 @@ self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL) self.assertEqual(session.disabled_hook_categories, set()) self.assertEqual(session.enabled_hook_categories, set()) - self.assertEqual(len(session._tx_data), 1) + self.assertEqual(len(session._txs), 1) with session.deny_all_hooks_but('metadata'): self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL) self.assertEqual(session.disabled_hook_categories, set()) @@ -54,7 +54,7 @@ self.assertEqual(session.enabled_hook_categories, set(('metadata',))) # leaving context manager with no transaction running should reset the # transaction local storage (and associated cnxset) - self.assertEqual(session._tx_data, {}) + self.assertEqual(session._txs, {}) self.assertEqual(session.cnxset, None) self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL) self.assertEqual(session.disabled_hook_categories, set()) diff -r c4aa23af0baa -r 064231ce93d5 server/test/unittest_undo.py --- a/server/test/unittest_undo.py Thu Mar 28 12:32:26 2013 +0100 +++ b/server/test/unittest_undo.py Thu Mar 28 12:41:14 2013 +0100 @@ -19,6 +19,8 @@ from cubicweb import ValidationError from cubicweb.devtools.testlib import CubicWebTC +import cubicweb.server.session +from cubicweb.server.session import Transaction as OldTransaction from cubicweb.transaction import * from cubicweb.server.sources.native import UndoTransactionException, _UndoException @@ -28,12 +30,19 @@ def setup_database(self): req = self.request() - self.session.undo_actions = True self.toto = self.create_user(req, 'toto', password='toto', groups=('users',), commit=False) self.txuuid = self.commit() + def setUp(self): + class Transaction(OldTransaction): + """Force undo feature to be turned on in all case""" + undo_actions = property(lambda tx: True, lambda x, y:None) + cubicweb.server.session.Transaction = Transaction + super(UndoableTransactionTC, self).setUp() + def tearDown(self): + cubicweb.server.session.Transaction = OldTransaction self.restore_connection() self.session.undo_support = set() super(UndoableTransactionTC, self).tearDown() diff -r c4aa23af0baa -r 064231ce93d5 skeleton/debian/control.tmpl --- a/skeleton/debian/control.tmpl Thu Mar 28 12:32:26 2013 +0100 +++ b/skeleton/debian/control.tmpl Thu Mar 28 12:41:14 2013 +0100 @@ -2,9 +2,9 @@ Section: web Priority: optional Maintainer: %(author)s <%(author-email)s> -Build-Depends: debhelper (>= 7), python (>=2.5), python-support +Build-Depends: debhelper (>= 7), python (>= 2.6), python-support Standards-Version: 3.9.3 -XS-Python-Version: >= 2.5 +XS-Python-Version: >= 2.6 Package: %(distname)s Architecture: all diff -r c4aa23af0baa -r 064231ce93d5 sobjects/cwxmlparser.py --- a/sobjects/cwxmlparser.py Thu Mar 28 12:32:26 2013 +0100 +++ b/sobjects/cwxmlparser.py Thu Mar 28 12:41:14 2013 +0100 @@ -42,7 +42,7 @@ from yams.constraints import BASE_CONVERTERS from yams.schema import role_name as rn -from cubicweb import ValidationError, RegistryException, typed_eid +from cubicweb import ValidationError, RegistryException from cubicweb.view import Component from cubicweb.server.sources import datafeed from cubicweb.server.hook import match_rtype @@ -326,10 +326,10 @@ item['cwtype'] = unicode(node.tag) item.setdefault('cwsource', None) try: - item['eid'] = typed_eid(item['eid']) + item['eid'] = int(item['eid']) except KeyError: # cw < 3.11 compat mode XXX - item['eid'] = typed_eid(node.find('eid').text) + item['eid'] = int(node.find('eid').text) item['cwuri'] = node.find('cwuri').text rels = {} for child in node: diff -r c4aa23af0baa -r 064231ce93d5 sobjects/textparsers.py --- a/sobjects/textparsers.py Thu Mar 28 12:32:26 2013 +0100 +++ b/sobjects/textparsers.py Thu Mar 28 12:41:14 2013 +0100 @@ -26,7 +26,7 @@ import re -from cubicweb import UnknownEid, typed_eid +from cubicweb import UnknownEid from cubicweb.view import Component @@ -66,7 +66,7 @@ def parse(self, caller, text): for trname, eid in self.instr_rgx.findall(text): try: - entity = self._cw.entity_from_eid(typed_eid(eid)) + entity = self._cw.entity_from_eid(int(eid)) except UnknownEid: self.error("can't get entity with eid %s", eid) continue diff -r c4aa23af0baa -r 064231ce93d5 test/unittest_vregistry.py --- a/test/unittest_vregistry.py Thu Mar 28 12:32:26 2013 +0100 +++ b/test/unittest_vregistry.py Thu Mar 28 12:41:14 2013 +0100 @@ -54,23 +54,23 @@ def test_load_subinterface_based_appobjects(self): - self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')]) - # check progressbar was kicked - self.assertFalse(self.vreg['views'].get('progressbar')) + self.vreg.register_objects([join(BASE, 'web', 'views', 'idownloadable.py')]) + # check downloadlink was kicked + self.assertFalse(self.vreg['views'].get('downloadlink')) # we've to emulate register_objects to add custom MyCard objects path = [join(BASE, 'entities', '__init__.py'), join(BASE, 'entities', 'adapters.py'), - join(BASE, 'web', 'views', 'iprogress.py')] + join(BASE, 'web', 'views', 'idownloadable.py')] filemods = self.vreg.init_registration(path, None) for filepath, modname in filemods: self.vreg.load_file(filepath, modname) - class CardIProgressAdapter(EntityAdapter): - __regid__ = 'IProgress' + class CardIDownloadableAdapter(EntityAdapter): + __regid__ = 'IDownloadable' self.vreg._loadedmods[__name__] = {} - self.vreg.register(CardIProgressAdapter) + self.vreg.register(CardIDownloadableAdapter) self.vreg.initialization_completed() # check progressbar isn't kicked - self.assertEqual(len(self.vreg['views']['progressbar']), 1) + self.assertEqual(len(self.vreg['views']['downloadlink']), 1) def test_properties(self): self.vreg.reset() diff -r c4aa23af0baa -r 064231ce93d5 utils.py --- a/utils.py Thu Mar 28 12:32:26 2013 +0100 +++ b/utils.py Thu Mar 28 12:41:14 2013 +0100 @@ -263,10 +263,7 @@ def add_post_inline_script(self, content): self.post_inlined_scripts.append(content) - def add_onload(self, jscode, jsoncall=_MARKER): - if jsoncall is not _MARKER: - warn('[3.7] specifying jsoncall is not needed anymore', - DeprecationWarning, stacklevel=2) + def add_onload(self, jscode): self.add_post_inline_script(u"""$(cw).one('server-response', function(event) { %s});""" % jscode) @@ -567,14 +564,6 @@ return 'javascript: ' + PERCENT_IN_URLQUOTE_RE.sub(r'%25', javascript_code) -@deprecated('[3.7] merge_dicts is deprecated') -def merge_dicts(dict1, dict2): - """update a copy of `dict1` with `dict2`""" - dict1 = dict(dict1) - dict1.update(dict2) - return dict1 - - def parse_repo_uri(uri): """ transform a command line uri into a (protocol, hostport, appid), e.g: -> 'inmemory', None, '' diff -r c4aa23af0baa -r 064231ce93d5 web/data/cubicweb.iprogress.css --- a/web/data/cubicweb.iprogress.css Thu Mar 28 12:32:26 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/* - * :organization: Logilab - * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. - * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr - */ - -/******************************************************************************/ -/* progressbar */ -/******************************************************************************/ - -.done { background:red } - -.inprogress { background:green } - -.overpassed { background: yellow} - - -canvas.progressbar { - border:1px solid black; -} - -.progressbarback { - border: 1px solid #000000; - background: transparent; - height: 10px; - width: 100px; -} - -/******************************************************************************/ -/* progress table */ -/******************************************************************************/ - -table.progress { - /* The default table view */ - margin: 10px 0px 1em; - width: 100%; - font-size: 0.9167em; -} - -table.progress th { - white-space: nowrap; - font-weight: bold; - background: %(listingHeaderBgColor)s; - padding: 2px 4px; - font-size:8pt; -} - -table.progress th, -table.progress td { - border: 1px solid %(listingBorderColor)s; -} - -table.progress td { - text-align: right; - padding: 2px 3px; -} - -table.progress th.tdleft, -table.progress td.tdleft { - text-align: left; - padding: 2px 3px 2px 5px; -} - -table.progress tr.highlighted { - background-color: %(listingHighlightedBgColor)s; -} - -table.progress tr.highlighted .progressbarback { - border: 1px solid %(listingHighlightedBgColor)s; -} - -table.progress .progressbarback { - border: 1px solid #777; -} - -.progress_data { - padding-right: 3px; -} \ No newline at end of file diff -r c4aa23af0baa -r 064231ce93d5 web/data/cubicweb.iprogress.js --- a/web/data/cubicweb.iprogress.js Thu Mar 28 12:32:26 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -function ProgressBar() { - this.budget = 100; - this.todo = 100; - this.done = 100; - this.color_done = "green"; - this.color_budget = "blue"; - this.color_todo = "#cccccc"; // grey - this.height = 16; - this.middle = this.height / 2; - this.radius = 4; -} - -ProgressBar.prototype.draw_one_rect = function(ctx, pos, color, fill) { - ctx.beginPath(); - ctx.lineWidth = 1; - ctx.strokeStyle = color; - if (fill) { - ctx.fillStyle = color; - ctx.fillRect(0, 0, pos, this.middle * 2); - } else { - ctx.lineWidth = 2; - ctx.strokeStyle = "black"; - ctx.moveTo(pos, 0); - ctx.lineTo(pos, this.middle * 2); - ctx.stroke(); - } -}; - -ProgressBar.prototype.draw_one_circ = function(ctx, pos, color) { - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.strokeStyle = color; - ctx.moveTo(0, this.middle); - ctx.lineTo(pos, this.middle); - ctx.arc(pos, this.middle, this.radius, 0, Math.PI * 2, true); - ctx.stroke(); -}; - -ProgressBar.prototype.draw_circ = function(ctx) { - this.draw_one_circ(ctx, this.budget, this.color_budget); - this.draw_one_circ(ctx, this.todo, this.color_todo); - this.draw_one_circ(ctx, this.done, this.color_done); -}; - -ProgressBar.prototype.draw_rect = function(ctx) { - this.draw_one_rect(ctx, this.todo, this.color_todo, true); - this.draw_one_rect(ctx, this.done, this.color_done, true); - this.draw_one_rect(ctx, this.budget, this.color_budget, false); -}; - -function draw_progressbar(cid, done, todo, budget, color) { - var canvas = document.getElementById(cid); - if (canvas.getContext) { - var ctx = canvas.getContext("2d"); - var bar = new ProgressBar(); - bar.budget = budget; - bar.todo = todo; - bar.done = done; - bar.color_done = color; - bar.draw_rect(ctx); - } -} - diff -r c4aa23af0baa -r 064231ce93d5 web/facet.py --- a/web/facet.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/facet.py Thu Mar 28 12:41:14 2013 +0100 @@ -64,7 +64,7 @@ from rql import nodes, utils -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized from cubicweb.schema import display_name from cubicweb.uilib import css_em_num_value from cubicweb.utils import make_uid @@ -500,8 +500,7 @@ return FacetVocabularyWidget def get_selected(self): - return frozenset(typed_eid(eid) - for eid in self._cw.list_form_param(self.__regid__)) + return frozenset(int(eid) for eid in self._cw.list_form_param(self.__regid__)) def get_widget(self): """Return the widget instance to use to display this facet. diff -r c4aa23af0baa -r 064231ce93d5 web/request.py --- a/web/request.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/request.py Thu Mar 28 12:41:14 2013 +0100 @@ -23,6 +23,7 @@ import random import base64 import urllib +from StringIO import StringIO from hashlib import sha1 # pylint: disable=E0611 from Cookie import SimpleCookie from calendar import timegm @@ -114,6 +115,8 @@ self._headers_in.addRawHeader(k, v) #: form parameters self.setup_params(form) + #: received body + self.content = StringIO() #: dictionary that may be used to store request data that has to be #: shared among various components used to publish the request (views, #: controller, application...) diff -r c4aa23af0baa -r 064231ce93d5 web/test/unittest_views_basecontrollers.py --- a/web/test/unittest_views_basecontrollers.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/test/unittest_views_basecontrollers.py Thu Mar 28 12:41:14 2013 +0100 @@ -32,6 +32,8 @@ from cubicweb.utils import json_dumps from cubicweb.uilib import rql_for_eid from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError, RemoteCallFailed +import cubicweb.server.session +from cubicweb.server.session import Transaction as OldTransaction from cubicweb.entities.authobjs import CWUser from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize @@ -533,18 +535,6 @@ p.__class__.skip_copy_for = old_skips -class EmbedControllerTC(CubicWebTC): - - def test_nonregr_embed_publish(self): - # This test looks a bit stupid but at least it will probably - # fail if the controller API changes and if EmbedController is not - # updated (which is what happened before this test) - req = self.request() - req.form['url'] = 'http://www.logilab.fr/' - controller = self.vreg['controllers'].select('embed', req) - result = controller.publish(rset=None) - - class ReportBugControllerTC(CubicWebTC): def test_usable_by_guest(self): @@ -793,9 +783,20 @@ class UndoControllerTC(CubicWebTC): + def setUp(self): + class Transaction(OldTransaction): + """Force undo feature to be turned on in all case""" + undo_actions = property(lambda tx: True, lambda x, y:None) + cubicweb.server.session.Transaction = Transaction + super(UndoControllerTC, self).setUp() + + def tearDown(self): + super(UndoControllerTC, self).tearDown() + cubicweb.server.session.Transaction = OldTransaction + + def setup_database(self): req = self.request() - self.session.undo_actions = True self.toto = self.create_user(req, 'toto', password='toto', groups=('users',), commit=False) self.txuuid_toto = self.commit() diff -r c4aa23af0baa -r 064231ce93d5 web/test/unittest_views_embeding.py --- a/web/test/unittest_views_embeding.py Thu Mar 28 12:32:26 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -""" - -""" - -from logilab.common.testlib import TestCase, unittest_main - -from cubicweb.web.views.embedding import prefix_links - -class UILIBTC(TestCase): - - - def test_prefix_links(self): - """suppose we are embedding http://embedded.com/page1.html""" - orig = ['perdu ?', - 'perdu ?', - 'perdu ?', - 'perdu ?', - '', - '', - '', - ] - expected = ['perdu ?', - 'perdu ?', - 'perdu ?', - 'perdu ?', - '', - '', - '', - ] - for orig_a, expected_a in zip(orig, expected): - got = prefix_links(orig_a, 'PREFIX', 'http://embedded.com/page1.html') - self.assertEqual(got, expected_a) - -if __name__ == '__main__': - unittest_main() - diff -r c4aa23af0baa -r 064231ce93d5 web/views/autoform.py --- a/web/views/autoform.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/autoform.py Thu Mar 28 12:41:14 2013 +0100 @@ -127,7 +127,7 @@ from logilab.common.decorators import iclassmethod, cached from logilab.common.deprecation import deprecated -from cubicweb import typed_eid, neg_role, uilib +from cubicweb import neg_role, uilib from cubicweb.schema import display_name from cubicweb.view import EntityView from cubicweb.predicates import ( @@ -415,7 +415,7 @@ subjs, rtype, objs = rstr.split(':') for subj in subjs.split('_'): for obj in objs.split('_'): - yield typed_eid(subj), rtype, typed_eid(obj) + yield int(subj), rtype, int(obj) def delete_relations(req, rdefs): """delete relations from the repository""" @@ -460,12 +460,12 @@ def _add_pending(req, eidfrom, rel, eidto, kind): key = 'pending_%s' % kind pendings = req.session.data.setdefault(key, set()) - pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) + pendings.add( (int(eidfrom), rel, int(eidto)) ) def _remove_pending(req, eidfrom, rel, eidto, kind): key = 'pending_%s' % kind pendings = req.session.data[key] - pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) ) + pendings.remove( (int(eidfrom), rel, int(eidto)) ) @ajaxfunc(output_type='json') def remove_pending_insert(self, (eidfrom, rel, eidto)): @@ -606,7 +606,7 @@ for pendingid in pending_inserts: eidfrom, rtype, eidto = pendingid.split(':') pendingid = 'id' + pendingid - if typed_eid(eidfrom) == entity.eid: # subject + if int(eidfrom) == entity.eid: # subject label = display_name(form._cw, rtype, 'subject', entity.__regid__) reid = eidto diff -r c4aa23af0baa -r 064231ce93d5 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/basecontrollers.py Thu Mar 28 12:41:14 2013 +0100 @@ -27,7 +27,7 @@ from logilab.common.deprecation import deprecated from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError, - AuthenticationError, typed_eid, UndoTransactionException, + AuthenticationError, UndoTransactionException, Forbidden) from cubicweb.utils import json_dumps from cubicweb.predicates import (authenticated_user, anonymous_user, @@ -176,7 +176,7 @@ if not '__linkto' in req.form: return if eid is None: - eid = typed_eid(req.form['eid']) + eid = int(req.form['eid']) for linkto in req.list_form_param('__linkto', pop=True): rtype, eids, target = linkto.split(':') assert target in ('subject', 'object') @@ -186,7 +186,7 @@ else: rql = 'SET Y %s X WHERE X eid %%(x)s, Y eid %%(y)s' % rtype for teid in eids: - req.execute(rql, {'x': eid, 'y': typed_eid(teid)}) + req.execute(rql, {'x': eid, 'y': int(teid)}) def _validation_error(req, ex): diff -r c4aa23af0baa -r 064231ce93d5 web/views/bookmark.py --- a/web/views/bookmark.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/bookmark.py Thu Mar 28 12:41:14 2013 +0100 @@ -22,7 +22,7 @@ from logilab.mtconverter import xml_escape -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized from cubicweb.predicates import is_instance, one_line_rset from cubicweb.web import action, component, htmlwidgets, formwidgets as fw from cubicweb.web.views import uicfg, primary @@ -137,4 +137,4 @@ @ajaxfunc def delete_bookmark(self, beid): rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s' - self._cw.execute(rql, {'b': typed_eid(beid), 'u' : self._cw.user.eid}) + self._cw.execute(rql, {'b': int(beid), 'u' : self._cw.user.eid}) diff -r c4aa23af0baa -r 064231ce93d5 web/views/editcontroller.py --- a/web/views/editcontroller.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/editcontroller.py Thu Mar 28 12:41:14 2013 +0100 @@ -25,7 +25,7 @@ from rql.utils import rqlvar_maker -from cubicweb import Binary, ValidationError, typed_eid +from cubicweb import Binary, ValidationError from cubicweb.view import EntityAdapter, implements_adapter_compat from cubicweb.predicates import is_instance from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, @@ -67,7 +67,7 @@ def valerror_eid(eid): try: - return typed_eid(eid) + return int(eid) except (ValueError, TypeError): return eid @@ -217,7 +217,7 @@ todelete = self._cw.list_form_param('__delete', formparams, pop=True) autoform.delete_relations(self._cw, todelete) if '__cloned_eid' in formparams: - entity.copy_relations(typed_eid(formparams['__cloned_eid'])) + entity.copy_relations(int(formparams['__cloned_eid'])) if is_main_entity: # only execute linkto for the main entity self.execute_linkto(entity.eid) return eid diff -r c4aa23af0baa -r 064231ce93d5 web/views/editviews.py --- a/web/views/editviews.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/editviews.py Thu Mar 28 12:41:14 2013 +0100 @@ -23,7 +23,6 @@ from logilab.common.decorators import cached from logilab.mtconverter import xml_escape -from cubicweb import typed_eid from cubicweb.view import EntityView, StartupView from cubicweb.predicates import (one_line_rset, non_final_entity, match_search_state) @@ -53,7 +52,7 @@ def filter_box_context_info(self): entity = self.cw_rset.get_entity(0, 0) role, eid, rtype, etype = self._cw.search_state[1] - assert entity.eid == typed_eid(eid) + assert entity.eid == int(eid) # the default behaviour is to fetch all unrelated entities and display # them. Use fetch_order and not fetch_unrelated_order as sort method # since the latter is mainly there to select relevant items in the combo diff -r c4aa23af0baa -r 064231ce93d5 web/views/embedding.py --- a/web/views/embedding.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/embedding.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -19,172 +19,20 @@ functionality. """ -__docformat__ = "restructuredtext en" -_ = unicode - -import re -from urlparse import urljoin -from urllib2 import urlopen, Request, HTTPError -from urllib import quote as urlquote # XXX should use view.url_quote method - -from logilab.mtconverter import guess_encoding - -from cubicweb.predicates import (one_line_rset, score_entity, implements, - adaptable, match_search_state) -from cubicweb.interfaces import IEmbedable -from cubicweb.view import NOINDEX, NOFOLLOW, EntityAdapter, implements_adapter_compat -from cubicweb.uilib import soup2xhtml -from cubicweb.web.controller import Controller -from cubicweb.web.action import Action -from cubicweb.web.views import basetemplates - - -class IEmbedableAdapter(EntityAdapter): - """interface for embedable entities""" - __needs_bw_compat__ = True - __regid__ = 'IEmbedable' - __select__ = implements(IEmbedable, warn=False) # XXX for bw compat, should be abstract - - @implements_adapter_compat('IEmbedable') - def embeded_url(self): - """embed action interface""" - raise NotImplementedError - - -class ExternalTemplate(basetemplates.TheMainTemplate): - """template embeding an external web pages into CubicWeb web interface - """ - __regid__ = 'external' +from logilab.common.deprecation import class_moved, moved - def call(self, body): - # XXX fallback to HTML 4 mode when embeding ? - self.set_request_content_type() - self._cw.search_state = ('normal',) - self.template_header(self.content_type, None, self._cw._('external page'), - [NOINDEX, NOFOLLOW]) - self.content_header() - self.w(body) - self.content_footer() - self.template_footer() - - -class EmbedController(Controller): - __regid__ = 'embed' - template = 'external' - - def publish(self, rset=None): - req = self._cw - if 'custom_css' in req.form: - req.add_css(req.form['custom_css']) - embedded_url = req.form['url'] - allowed = self._cw.vreg.config['embed-allowed'] - _ = req._ - if allowed is None or not allowed.match(embedded_url): - body = '

%s

%s

' % ( - _('error while embedding page'), - _('embedding this url is forbidden')) - else: - prefix = req.build_url(self.__regid__, url='') - authorization = req.get_header('Authorization') - if authorization: - headers = {'Authorization' : authorization} - else: - headers = {} - try: - body = embed_external_page(embedded_url, prefix, - headers, req.form.get('custom_css')) - body = soup2xhtml(body, self._cw.encoding) - except HTTPError as err: - body = '

%s

%s

' % ( - _('error while embedding page'), err) - rset = self.process_rql() - return self._cw.vreg['views'].main_template(req, self.template, - rset=rset, body=body) - +try: + from cubes.embed.views import * -def entity_has_embedable_url(entity): - """return 1 if the entity provides an allowed embedable url""" - url = entity.cw_adapt_to('IEmbedable').embeded_url() - if not url or not url.strip(): - return 0 - allowed = entity._cw.vreg.config['embed-allowed'] - if allowed is None or not allowed.match(url): - return 0 - return 1 - - -class EmbedAction(Action): - """display an 'embed' link on entity implementing `embeded_url` method - if the returned url match embeding configuration - """ - __regid__ = 'embed' - __select__ = (one_line_rset() & match_search_state('normal') - & adaptable('IEmbedable') - & score_entity(entity_has_embedable_url)) - - title = _('embed') - - def url(self, row=0): - entity = self.cw_rset.get_entity(row, 0) - url = urljoin(self._cw.base_url(), entity.cw_adapt_to('IEmbedable').embeded_url()) - if 'rql' in self._cw.form: - return self._cw.build_url('embed', url=url, rql=self._cw.form['rql']) - return self._cw.build_url('embed', url=url) - - - -# functions doing necessary substitutions to embed an external html page ###### - - -BODY_RGX = re.compile('(.*?)', re.I | re.S | re.U) -HREF_RGX = re.compile('. -"""Specific views for entities implementing IProgress/IMileStone""" - -__docformat__ = "restructuredtext en" -_ = unicode - -from math import floor - -from logilab.common.deprecation import class_deprecated -from logilab.mtconverter import xml_escape - -from cubicweb.utils import make_uid -from cubicweb.predicates import adaptable -from cubicweb.schema import display_name -from cubicweb.view import EntityView -from cubicweb.web.views.tableview import EntityAttributesTableView - - -class ProgressTableView(EntityAttributesTableView): - """The progress table view is able to display progress information - of any object implement IMileStone. - - The default layout is composoed of 7 columns : parent task, - milestone, state, estimated date, cost, progressbar, and todo_by - - The view accepts an optional ``columns`` paramater that lets you - remove or reorder some of those columns. - - To add new columns, you should extend this class, define a new - ``columns`` class attribute and implement corresponding - build_COLNAME_cell methods - - header_for_COLNAME methods allow to customize header's label - """ - __metaclass__ = class_deprecated - __deprecation_warning__ = '[3.14] %(cls)s is deprecated' - - __regid__ = 'progress_table_view' - __select__ = adaptable('IMileStone') - title = _('task progression') - table_css = "progress" - css_files = ('cubicweb.iprogress.css',) - - # default columns of the table - columns = (_('project'), _('milestone'), _('state'), _('eta_date'), - _('cost'), _('progress'), _('todo_by')) - - def cell_call(self, row, col): - _ = self._cw._ - entity = self.cw_rset.get_entity(row, col) - infos = {} - for col in self.columns: - meth = getattr(self, 'build_%s_cell' % col, None) - # find the build method or try to find matching attribute - if meth: - content = meth(entity) - else: - content = entity.printable_value(col) - infos[col] = content - cssclass = entity.cw_adapt_to('IMileStone').progress_class() - self.w(u"""""" % cssclass) - line = u''.join(u'%%(%s)s' % col for col in self.columns) - self.w(line % infos) - self.w(u'\n') - - ## header management ###################################################### - - def header_for_project(self, sample): - """use entity's parent type as label""" - return display_name(self._cw, sample.cw_adapt_to('IMileStone').parent_type) - - def header_for_milestone(self, sample): - """use entity's type as label""" - return display_name(self._cw, sample.__regid__) - - ## cell management ######################################################## - def build_project_cell(self, entity): - """``project`` column cell renderer""" - project = entity.cw_adapt_to('IMileStone').get_main_task() - if project: - return project.view('incontext') - return self._cw._('no related project') - - def build_milestone_cell(self, entity): - """``milestone`` column cell renderer""" - return entity.view('incontext') - - def build_state_cell(self, entity): - """``state`` column cell renderer""" - return xml_escape(entity.cw_adapt_to('IWorkflowable').printable_state) - - def build_eta_date_cell(self, entity): - """``eta_date`` column cell renderer""" - imilestone = entity.cw_adapt_to('IMileStone') - if imilestone.finished(): - return self._cw.format_date(imilestone.completion_date()) - formated_date = self._cw.format_date(imilestone.initial_prevision_date()) - if imilestone.in_progress(): - eta_date = self._cw.format_date(imilestone.eta_date()) - _ = self._cw._ - if formated_date: - formated_date += u' (%s %s)' % (_('expected:'), eta_date) - else: - formated_date = u'%s %s' % (_('expected:'), eta_date) - return formated_date - - def build_todo_by_cell(self, entity): - """``todo_by`` column cell renderer""" - imilestone = entity.cw_adapt_to('IMileStone') - return u', '.join(p.view('outofcontext') for p in imilestone.contractors()) - - def build_cost_cell(self, entity): - """``cost`` column cell renderer""" - _ = self._cw._ - imilestone = entity.cw_adapt_to('IMileStone') - pinfo = imilestone.progress_info() - totalcost = pinfo.get('estimatedcorrected', pinfo['estimated']) - missing = pinfo.get('notestimatedcorrected', pinfo.get('notestimated', 0)) - costdescr = [] - if missing: - # XXX: link to unestimated entities - costdescr.append(_('%s not estimated') % missing) - estimated = pinfo['estimated'] - if estimated and estimated != totalcost: - costdescr.append(_('initial estimation %s') % estimated) - if costdescr: - return u'%s (%s)' % (totalcost, ', '.join(costdescr)) - return unicode(totalcost) - - def build_progress_cell(self, entity): - """``progress`` column cell renderer""" - return entity.view('progressbar') - - -class InContextProgressTableView(ProgressTableView): - """this views redirects to ``progress_table_view`` but removes - the ``project`` column - """ - __metaclass__ = class_deprecated - __deprecation_warning__ = '[3.14] %(cls)s is deprecated' - __regid__ = 'ic_progress_table_view' - - def call(self, columns=None): - view = self._cw.vreg['views'].select('progress_table_view', self._cw, - rset=self.cw_rset) - columns = list(columns or view.columns) - try: - columns.remove('project') - except ValueError: - self.info('[ic_progress_table_view] could not remove project from columns') - view.render(w=self.w, columns=columns) - - -class ProgressBarView(EntityView): - """displays a progress bar""" - __metaclass__ = class_deprecated - __deprecation_warning__ = '[3.14] %(cls)s is deprecated' - __regid__ = 'progressbar' - __select__ = adaptable('IProgress') - - title = _('progress bar') - - precision = 0.1 - red_threshold = 1.1 - orange_threshold = 1.05 - yellow_threshold = 1 - - @classmethod - def overrun(cls, iprogress): - done = iprogress.done or 0 - todo = iprogress.todo or 0 - budget = iprogress.revised_cost or 0 - if done + todo > budget: - overrun = done + todo - budget - else: - overrun = 0 - if overrun < cls.precision: - overrun = 0 - return overrun - - @classmethod - def overrun_percentage(cls, iprogress): - budget = iprogress.revised_cost or 0 - if budget == 0: - return 0 - return cls.overrun(iprogress) * 100. / budget - - def cell_call(self, row, col): - self._cw.add_css('cubicweb.iprogress.css') - self._cw.add_js('cubicweb.iprogress.js') - entity = self.cw_rset.get_entity(row, col) - iprogress = entity.cw_adapt_to('IProgress') - done = iprogress.done or 0 - todo = iprogress.todo or 0 - budget = iprogress.revised_cost or 0 - if budget == 0: - pourcent = 100 - else: - pourcent = done*100./budget - if pourcent > 100.1: - color = 'red' - elif todo+done > self.red_threshold*budget: - color = 'red' - elif todo+done > self.orange_threshold*budget: - color = 'orange' - elif todo+done > self.yellow_threshold*budget: - color = 'yellow' - else: - color = 'green' - if pourcent < 0: - pourcent = 0 - - if floor(done) == done or done>100: - done_str = '%i' % done - else: - done_str = '%.1f' % done - if floor(budget) == budget or budget>100: - budget_str = '%i' % budget - else: - budget_str = '%.1f' % budget - - title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent) - short_title = title - overrunpercent = self.overrun_percentage(iprogress) - if overrunpercent: - overrun = self.overrun(iprogress) - title += u' overrun +%sj (+%i%%)' % (overrun, overrunpercent) - if floor(overrun) == overrun or overrun > 100: - short_title += u' +%i' % overrun - else: - short_title += u' +%.1f' % overrun - # write bars - maxi = max(done+todo, budget) - if maxi == 0: - maxi = 1 - cid = make_uid('progress_bar') - self._cw.html_headers.add_onload( - 'draw_progressbar("canvas%s", %i, %i, %i, "%s");' % - (cid, int(100.*done/maxi), int(100.*(done+todo)/maxi), - int(100.*budget/maxi), color)) - self.w(u'%s
' - u'' - % (xml_escape(short_title), cid)) diff -r c4aa23af0baa -r 064231ce93d5 web/views/isioc.py --- a/web/views/isioc.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/isioc.py Thu Mar 28 12:41:14 2013 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -20,144 +20,16 @@ http://sioc-project.org """ -__docformat__ = "restructuredtext en" -_ = unicode - -from logilab.mtconverter import xml_escape - -from cubicweb.view import EntityView, EntityAdapter, implements_adapter_compat -from cubicweb.predicates import implements, adaptable -from cubicweb.interfaces import ISiocItem, ISiocContainer - - -class ISIOCItemAdapter(EntityAdapter): - """interface for entities which may be represented as an ISIOC items""" - __needs_bw_compat__ = True - __regid__ = 'ISIOCItem' - __select__ = implements(ISiocItem, warn=False) # XXX for bw compat, should be abstract - - @implements_adapter_compat('ISIOCItem') - def isioc_content(self): - """return item's content""" - raise NotImplementedError - - @implements_adapter_compat('ISIOCItem') - def isioc_container(self): - """return container entity""" - raise NotImplementedError - - @implements_adapter_compat('ISIOCItem') - def isioc_type(self): - """return container type (post, BlogPost, MailMessage)""" - raise NotImplementedError +from logilab.common.deprecation import class_moved - @implements_adapter_compat('ISIOCItem') - def isioc_replies(self): - """return replies items""" - raise NotImplementedError - - @implements_adapter_compat('ISIOCItem') - def isioc_topics(self): - """return topics items""" - raise NotImplementedError - - -class ISIOCContainerAdapter(EntityAdapter): - """interface for entities which may be represented as an ISIOC container""" - __needs_bw_compat__ = True - __regid__ = 'ISIOCContainer' - __select__ = implements(ISiocContainer, warn=False) # XXX for bw compat, should be abstract - - @implements_adapter_compat('ISIOCContainer') - def isioc_type(self): - """return container type (forum, Weblog, MailingList)""" - raise NotImplementedError - - @implements_adapter_compat('ISIOCContainer') - def isioc_items(self): - """return contained items""" - raise NotImplementedError - - -class SIOCView(EntityView): - __regid__ = 'sioc' - __select__ = adaptable('ISIOCItem', 'ISIOCContainer') - title = _('sioc') - templatable = False - content_type = 'text/xml' +try: + from cubes.sioc.views import * - def call(self): - self.w(u'\n' % self._cw.encoding) - self.w(u'''\n''') - for i in xrange(self.cw_rset.rowcount): - self.cell_call(i, 0) - self.w(u'\n') - - def cell_call(self, row, col): - self.wview('sioc_element', self.cw_rset, row=row, col=col) - -class SIOCContainerView(EntityView): - __regid__ = 'sioc_element' - __select__ = adaptable('ISIOCContainer') - templatable = False - content_type = 'text/xml' - - def cell_call(self, row, col): - entity = self.cw_rset.complete_entity(row, col) - isioc = entity.cw_adapt_to('ISIOCContainer') - isioct = isioc.isioc_type() - self.w(u'\n' - % (isioct, xml_escape(entity.absolute_url()))) - self.w(u'%s' - % xml_escape(entity.dc_title())) - self.w(u'%s' - % entity.creation_date) # XXX format - self.w(u'%s' - % entity.modification_date) # XXX format - self.w(u'')#entity.isioc_items() - self.w(u'\n' % isioct) - - -class SIOCItemView(EntityView): - __regid__ = 'sioc_element' - __select__ = adaptable('ISIOCItem') - templatable = False - content_type = 'text/xml' - - def cell_call(self, row, col): - entity = self.cw_rset.complete_entity(row, col) - isioc = entity.cw_adapt_to('ISIOCItem') - isioct = isioc.isioc_type() - self.w(u'\n' - % (isioct, xml_escape(entity.absolute_url()))) - self.w(u'%s' - % xml_escape(entity.dc_title())) - self.w(u'%s' - % entity.creation_date) # XXX format - self.w(u'%s' - % entity.modification_date) # XXX format - content = isioc.isioc_content() - if content: - self.w(u'%s' % xml_escape(content)) - container = isioc.isioc_container() - if container: - self.w(u'\n' - % xml_escape(container.absolute_url())) - if entity.creator: - self.w(u'\n') - self.w(u'\n' - % xml_escape(entity.creator.absolute_url())) - self.w(entity.creator.view('foaf')) - self.w(u'\n') - self.w(u'\n') - self.w(u'')#entity.isioc_topics() - self.w(u'')#entity.isioc_replies() - self.w(u' \n' % isioct) - + ISIOCItemAdapter = class_moved(ISIOCItemAdapter, message='[3.17] ISIOCItemAdapter moved to cubes.isioc.views') + ISIOCContainerAdapter = class_moved(ISIOCContainerAdapter, message='[3.17] ISIOCContainerAdapter moved to cubes.isioc.views') + SIOCView = class_moved(SIOCView, message='[3.17] SIOCView moved to cubes.is.view') + SIOCContainerView = class_moved(SIOCContainerView, message='[3.17] SIOCContainerView moved to cubes.is.view') + SIOCItemView = class_moved(SIOCItemView, message='[3.17] SIOCItemView moved to cubes.is.view') +except ImportError: + from cubicweb.web import LOGGER + LOGGER.warning('[3.17] isioc extracted to cube sioc that was not found. try installing it.') diff -r c4aa23af0baa -r 064231ce93d5 web/views/magicsearch.py --- a/web/views/magicsearch.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/magicsearch.py Thu Mar 28 12:41:14 2013 +0100 @@ -29,7 +29,7 @@ from rql.utils import rqlvar_maker from rql.nodes import Relation -from cubicweb import Unauthorized, typed_eid +from cubicweb import Unauthorized from cubicweb.view import Component from cubicweb.web.views.ajaxcontroller import ajaxfunc @@ -254,7 +254,7 @@ """ # if this is an integer, then directly go to eid try: - eid = typed_eid(word) + eid = int(word) return 'Any X WHERE X eid %(x)s', {'x': eid}, 'x' except ValueError: etype = self._get_entity_type(word) diff -r c4aa23af0baa -r 064231ce93d5 web/views/reledit.py --- a/web/views/reledit.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/reledit.py Thu Mar 28 12:41:14 2013 +0100 @@ -29,7 +29,7 @@ from logilab.common.deprecation import deprecated, class_renamed from logilab.common.decorators import cached -from cubicweb import neg_role, typed_eid +from cubicweb import neg_role from cubicweb.schema import display_name from cubicweb.utils import json, json_dumps from cubicweb.predicates import non_final_entity, match_kwargs @@ -402,7 +402,7 @@ req = self._cw args = dict((x, req.form[x]) for x in ('formid', 'rtype', 'role', 'reload', 'action')) - rset = req.eid_rset(typed_eid(self._cw.form['eid'])) + rset = req.eid_rset(int(self._cw.form['eid'])) try: args['reload'] = json.loads(args['reload']) except ValueError: # not true/false, an absolute url diff -r c4aa23af0baa -r 064231ce93d5 web/views/urlpublishing.py --- a/web/views/urlpublishing.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/urlpublishing.py Thu Mar 28 12:41:14 2013 +0100 @@ -59,7 +59,7 @@ from rql import TypeResolverException -from cubicweb import RegistryException, typed_eid +from cubicweb import RegistryException from cubicweb.web import NotFound, Redirect, component @@ -165,7 +165,7 @@ if len(parts) != 1: raise PathDontMatch() try: - rset = req.execute('Any X WHERE X eid %(x)s', {'x': typed_eid(parts[0])}) + rset = req.execute('Any X WHERE X eid %(x)s', {'x': int(parts[0])}) except ValueError: raise PathDontMatch() if rset.rowcount == 0: @@ -222,7 +222,7 @@ 'x', 'Substitute') if attrname == 'eid': try: - rset = req.execute(st.as_string(), {'x': typed_eid(value)}) + rset = req.execute(st.as_string(), {'x': int(value)}) except (ValueError, TypeResolverException): # conflicting eid/type raise PathDontMatch() diff -r c4aa23af0baa -r 064231ce93d5 web/views/urlrewrite.py --- a/web/views/urlrewrite.py Thu Mar 28 12:32:26 2013 +0100 +++ b/web/views/urlrewrite.py Thu Mar 28 12:41:14 2013 +0100 @@ -19,7 +19,6 @@ import re -from cubicweb import typed_eid from cubicweb.uilib import domid from cubicweb.appobject import AppObject @@ -186,7 +185,7 @@ except KeyError: kwargs[key] = value if cachekey is not None and key in cachekey: - kwargs[key] = typed_eid(value) + kwargs[key] = int(value) if setuser: kwargs['u'] = req.user.eid for param in rqlformparams: diff -r c4aa23af0baa -r 064231ce93d5 wsgi/request.py --- a/wsgi/request.py Thu Mar 28 12:32:26 2013 +0100 +++ b/wsgi/request.py Thu Mar 28 12:41:14 2013 +0100 @@ -38,13 +38,14 @@ class CubicWebWsgiRequest(CubicWebRequestBase): - """most of this code COMES FROM DJANO + """most of this code COMES FROM DJANGO """ def __init__(self, environ, vreg): self.environ = environ self.path = environ['PATH_INFO'] self.method = environ['REQUEST_METHOD'].upper() + self.content = environ['wsgi.input'] headers_in = dict((normalize_header(k[5:]), v) for k, v in self.environ.items() if k.startswith('HTTP_')) diff -r c4aa23af0baa -r 064231ce93d5 xy.py --- a/xy.py Thu Mar 28 12:32:26 2013 +0100 +++ b/xy.py Thu Mar 28 12:41:14 2013 +0100 @@ -23,7 +23,6 @@ xy.register_prefix('dc', 'http://purl.org/dc/elements/1.1/') xy.register_prefix('foaf', 'http://xmlns.com/foaf/0.1/') xy.register_prefix('doap', 'http://usefulinc.com/ns/doap#') -xy.register_prefix('sioc', 'http://rdfs.org/sioc/ns#') xy.register_prefix('owl', 'http://www.w3.org/2002/07/owl#') xy.register_prefix('dcterms', 'http://purl.org/dc/terms/')