# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1401807434 -7200
# Tue Jun 03 16:57:14 2014 +0200
# Node ID 6f25c7e4f19bb7be8bf4e30f3bb7f1b10eaa863b
# Parent 6c12264b3f1895f88c19c66846a61467c3ffc7b7
[dbapi] remove the dbapi module and its immediate remaining users
We suppress toolsutils.config_connect, which has currently
only one known user (the email cube), which is being patched.
It can be replaced with utils.admincnx function for a local
access.
Next will come a series to:
* remove the session backward compatibility
* fold ClientConnection into Connection
Closes #3933480.
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1401807434 -7200
# Tue Jun 03 16:57:14 2014 +0200
# Node ID 6f25c7e4f19bb7be8bf4e30f3bb7f1b10eaa863b
# Parent 6c12264b3f1895f88c19c66846a61467c3ffc7b7
[dbapi] remove the dbapi module and its immediate remaining users
We suppress toolsutils.config_connect, which has currently
only one known user (the email cube), which is being patched.
It can be replaced with utils.admincnx function for a local
access.
Next will come a series to:
* remove the session backward compatibility
* fold ClientConnection into Connection
Closes #3933480.
@@ -17,23 +17,27 @@
1 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. 2 3 import gc, types, weakref 4 5 from cubicweb.schema import CubicWebRelationSchema, CubicWebEntitySchema 6 -from cubicweb.dbapi import _NeedAuthAccessMock 7 +try: 8 + from cubicweb.web.request import _NeedAuthAccessMock 9 +except ImportError: 10 + _NeedAuthAccessMock = None 11 12 listiterator = type(iter([])) 13 14 IGNORE_CLASSES = ( 15 type, tuple, dict, list, set, frozenset, type(len), 16 weakref.ref, weakref.WeakKeyDictionary, 17 listiterator, 18 property, classmethod, 19 types.ModuleType, types.FunctionType, types.MethodType, 20 types.MemberDescriptorType, types.GetSetDescriptorType, 21 - _NeedAuthAccessMock, 22 ) 23 +if _NeedAuthAccessMock is not None: 24 + IGNORE_CLASSES = IGNORE_CLASSES + (_NeedAuthAccessMock,) 25 26 def _get_counted_class(obj, classes): 27 for cls in classes: 28 if isinstance(obj, cls): 29 return cls
@@ -1,796 +0,0 @@
30 -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 31 -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 32 -# 33 -# This file is part of CubicWeb. 34 -# 35 -# CubicWeb is free software: you can redistribute it and/or modify it under the 36 -# terms of the GNU Lesser General Public License as published by the Free 37 -# Software Foundation, either version 2.1 of the License, or (at your option) 38 -# any later version. 39 -# 40 -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT 41 -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 42 -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 43 -# details. 44 -# 45 -# You should have received a copy of the GNU Lesser General Public License along 46 -# with CubicWeb. If not, see <http://www.gnu.org/licenses/>. 47 -"""DB-API 2.0 compliant module 48 - 49 -Take a look at http://www.python.org/peps/pep-0249.html 50 - 51 -(most parts of this document are reported here in docstrings) 52 -""" 53 - 54 -__docformat__ = "restructuredtext en" 55 - 56 -from threading import currentThread 57 -from logging import getLogger 58 -from time import time, clock 59 -from itertools import count 60 -from warnings import warn 61 -from os.path import join 62 -from uuid import uuid4 63 -from urlparse import urlparse 64 - 65 -from logilab.common.logging_ext import set_log_methods 66 -from logilab.common.decorators import monkeypatch, cachedproperty 67 -from logilab.common.deprecation import deprecated 68 - 69 -from cubicweb import (ETYPE_NAME_MAP, AuthenticationError, ProgrammingError, 70 - cwvreg, cwconfig) 71 -from cubicweb.repoapi import get_repository 72 -from cubicweb.req import RequestSessionBase 73 - 74 - 75 -_MARKER = object() 76 - 77 -def _fake_property_value(self, name): 78 - try: 79 - return super(DBAPIRequest, self).property_value(name) 80 - except KeyError: 81 - return '' 82 - 83 -def fake(*args, **kwargs): 84 - return None 85 - 86 -def multiple_connections_fix(): 87 - """some monkey patching necessary when an application has to deal with 88 - several connections to different repositories. It tries to hide buggy class 89 - attributes since classes are not designed to be shared among multiple 90 - registries. 91 - """ 92 - defaultcls = cwvreg.CWRegistryStore.REGISTRY_FACTORY[None] 93 - 94 - etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] 95 - orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class 96 - @monkeypatch(defaultcls) 97 - def etype_class(self, etype): 98 - """return an entity class for the given entity type. 99 - Try to find out a specific class for this kind of entity or 100 - default to a dump of the class registered for 'Any' 101 - """ 102 - usercls = orig_etype_class(self, etype) 103 - if etype == 'Any': 104 - return usercls 105 - usercls.e_schema = self.schema.eschema(etype) 106 - return usercls 107 - 108 -def multiple_connections_unfix(): 109 - etypescls = cwvreg.CWRegistryStore.REGISTRY_FACTORY['etypes'] 110 - etypescls.etype_class = etypescls.orig_etype_class 111 - 112 - 113 -class ConnectionProperties(object): 114 - def __init__(self, cnxtype=None, close=True, log=False): 115 - if cnxtype is not None: 116 - warn('[3.16] cnxtype argument is deprecated', DeprecationWarning, 117 - stacklevel=2) 118 - self.cnxtype = cnxtype 119 - self.log_queries = log 120 - self.close_on_del = close 121 - 122 - 123 -@deprecated('[3.19] the dbapi is deprecated. Have a look at the new repoapi.') 124 -def _repo_connect(repo, login, **kwargs): 125 - """Constructor to create a new connection to the given CubicWeb repository. 126 - 127 - Returns a Connection instance. 128 - 129 - Raises AuthenticationError if authentication failed 130 - """ 131 - cnxid = repo.connect(unicode(login), **kwargs) 132 - cnx = Connection(repo, cnxid, kwargs.get('cnxprops')) 133 - if cnx.is_repo_in_memory: 134 - cnx.vreg = repo.vreg 135 - return cnx 136 - 137 -def connect(database, login=None, 138 - cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs): 139 - """Constructor for creating a connection to the CubicWeb repository. 140 - Returns a :class:`Connection` object. 141 - 142 - Typical usage:: 143 - 144 - cnx = connect('myinstance', login='me', password='toto') 145 - 146 - `database` may be: 147 - 148 - * a simple instance id for in-memory connection 149 - 150 - * a uri like scheme://host:port/instanceid where scheme must be 151 - 'inmemory' 152 - 153 - Other arguments: 154 - 155 - :login: 156 - the user login to use to authenticate. 157 - 158 - :cnxprops: 159 - a :class:`ConnectionProperties` instance, allowing to specify 160 - the connection method (eg in memory). 161 - 162 - :setvreg: 163 - flag telling if a registry should be initialized for the connection. 164 - Don't change this unless you know what you're doing. 165 - 166 - :mulcnx: 167 - Will disappear at some point. Try to deal with connections to differents 168 - instances in the same process unless specified otherwise by setting this 169 - flag to False. Don't change this unless you know what you're doing. 170 - 171 - :initlog: 172 - flag telling if logging should be initialized. You usually don't want 173 - logging initialization when establishing the connection from a process 174 - where it's already initialized. 175 - 176 - :kwargs: 177 - there goes authentication tokens. You usually have to specify a password 178 - for the given user, using a named 'password' argument. 179 - 180 - """ 181 - if not urlparse(database).scheme: 182 - warn('[3.16] give an qualified URI as database instead of using ' 183 - 'host/cnxprops to specify the connection method', 184 - DeprecationWarning, stacklevel=2) 185 - puri = urlparse(database) 186 - method = puri.scheme.lower() 187 - assert method == 'inmemory' 188 - config = cwconfig.instance_configuration(puri.netloc) 189 - repo = get_repository(database, config=config) 190 - vreg = repo.vreg 191 - cnx = _repo_connect(repo, login, cnxprops=cnxprops, **kwargs) 192 - cnx.vreg = vreg 193 - return cnx 194 - 195 -def in_memory_repo(config): 196 - """Return and in_memory Repository object from a config (or vreg)""" 197 - if isinstance(config, cwvreg.CWRegistryStore): 198 - vreg = config 199 - config = None 200 - else: 201 - vreg = None 202 - # get local access to the repository 203 - return get_repository('inmemory://', config=config, vreg=vreg) 204 - 205 -def in_memory_repo_cnx(config, login, **kwargs): 206 - """useful method for testing and scripting to get a dbapi.Connection 207 - object connected to an in-memory repository instance 208 - """ 209 - # connection to the CubicWeb repository 210 - repo = in_memory_repo(config) 211 - return repo, _repo_connect(repo, login, **kwargs) 212 - 213 -# XXX web only method, move to webconfig? 214 -def anonymous_session(vreg): 215 - """return a new anonymous session 216 - 217 - raises an AuthenticationError if anonymous usage is not allowed 218 - """ 219 - anoninfo = vreg.config.anonymous_user() 220 - if anoninfo[0] is None: # no anonymous user 221 - raise AuthenticationError('anonymous access is not authorized') 222 - anon_login, anon_password = anoninfo 223 - # use vreg's repository cache 224 - repo = vreg.config.repository(vreg) 225 - anon_cnx = _repo_connect(repo, anon_login, password=anon_password) 226 - anon_cnx.vreg = vreg 227 - return DBAPISession(anon_cnx, anon_login) 228 - 229 - 230 -class _NeedAuthAccessMock(object): 231 - def __getattribute__(self, attr): 232 - raise AuthenticationError() 233 - def __nonzero__(self): 234 - return False 235 - 236 -class DBAPISession(object): 237 - def __init__(self, cnx, login=None): 238 - self.cnx = cnx 239 - self.data = {} 240 - self.login = login 241 - # dbapi session identifier is the same as the first connection 242 - # identifier, but may later differ in case of auto-reconnection as done 243 - # by the web authentication manager (in cw.web.views.authentication) 244 - if cnx is not None: 245 - self.sessionid = cnx.sessionid 246 - else: 247 - self.sessionid = uuid4().hex 248 - 249 - @property 250 - def anonymous_session(self): 251 - return not self.cnx or self.cnx.anonymous_connection 252 - 253 - def __repr__(self): 254 - return '<DBAPISession %r>' % self.sessionid 255 - 256 - 257 -class DBAPIRequest(RequestSessionBase): 258 - #: Request language identifier eg: 'en' 259 - lang = None 260 - 261 - def __init__(self, vreg, session=None): 262 - super(DBAPIRequest, self).__init__(vreg) 263 - #: 'language' => translation_function() mapping 264 - try: 265 - # no vreg or config which doesn't handle translations 266 - self.translations = vreg.config.translations 267 - except AttributeError: 268 - self.translations = {} 269 - #: cache entities built during the request 270 - self._eid_cache = {} 271 - if session is not None: 272 - self.set_session(session) 273 - else: 274 - # these args are initialized after a connection is 275 - # established 276 - self.session = DBAPISession(None) 277 - self.cnx = self.user = _NeedAuthAccessMock() 278 - self.set_default_language(vreg) 279 - 280 - def get_option_value(self, option, foreid=None): 281 - if foreid is not None: 282 - warn('[3.19] foreid argument is deprecated', DeprecationWarning, 283 - stacklevel=2) 284 - return self.cnx.get_option_value(option) 285 - 286 - def set_session(self, session): 287 - """method called by the session handler when the user is authenticated 288 - or an anonymous connection is open 289 - """ 290 - self.session = session 291 - if session.cnx: 292 - self.cnx = session.cnx 293 - self.execute = session.cnx.cursor(self).execute 294 - self.user = self.cnx.user(self) 295 - self.set_entity_cache(self.user) 296 - 297 - def execute(self, *args, **kwargs): # pylint: disable=E0202 298 - """overriden when session is set. By default raise authentication error 299 - so authentication is requested. 300 - """ 301 - raise AuthenticationError() 302 - 303 - def set_default_language(self, vreg): 304 - try: 305 - lang = vreg.property_value('ui.language') 306 - except Exception: # property may not be registered 307 - lang = 'en' 308 - try: 309 - self.set_language(lang) 310 - except KeyError: 311 - # this occurs usually during test execution 312 - self._ = self.__ = unicode 313 - self.pgettext = lambda x, y: unicode(y) 314 - 315 - # server-side service call ################################################# 316 - 317 - def call_service(self, regid, **kwargs): 318 - return self.cnx.call_service(regid, **kwargs) 319 - 320 - # entities cache management ############################################### 321 - 322 - def entity_cache(self, eid): 323 - return self._eid_cache[eid] 324 - 325 - def set_entity_cache(self, entity): 326 - self._eid_cache[entity.eid] = entity 327 - 328 - def cached_entities(self): 329 - return self._eid_cache.values() 330 - 331 - def drop_entity_cache(self, eid=None): 332 - if eid is None: 333 - self._eid_cache = {} 334 - else: 335 - del self._eid_cache[eid] 336 - 337 - # low level session data management ####################################### 338 - 339 - @deprecated('[3.19] use session or transaction data') 340 - def get_shared_data(self, key, default=None, pop=False, txdata=False): 341 - """see :meth:`Connection.get_shared_data`""" 342 - return self.cnx.get_shared_data(key, default, pop, txdata) 343 - 344 - @deprecated('[3.19] use session or transaction data') 345 - def set_shared_data(self, key, value, txdata=False, querydata=None): 346 - """see :meth:`Connection.set_shared_data`""" 347 - if querydata is not None: 348 - txdata = querydata 349 - warn('[3.10] querydata argument has been renamed to txdata', 350 - DeprecationWarning, stacklevel=2) 351 - return self.cnx.set_shared_data(key, value, txdata) 352 - 353 - # server session compat layer ############################################# 354 - 355 - def entity_metas(self, eid): 356 - """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" 357 - return self.cnx.entity_metas(eid) 358 - 359 - def source_defs(self): 360 - """return the definition of sources used by the repository.""" 361 - return self.cnx.source_defs() 362 - 363 - @deprecated('[3.19] use .entity_metas(eid) instead') 364 - def describe(self, eid, asdict=False): 365 - """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" 366 - return self.cnx.describe(eid, asdict) 367 - 368 - # these are overridden by set_log_methods below 369 - # only defining here to prevent pylint from complaining 370 - info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None 371 - 372 -set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi')) 373 - 374 - 375 - 376 -# cursor / connection objects ################################################## 377 - 378 -class Cursor(object): 379 - """These objects represent a database cursor, which is used to manage the 380 - context of a fetch operation. Cursors created from the same connection are 381 - not isolated, i.e., any changes done to the database by a cursor are 382 - immediately visible by the other cursors. Cursors created from different 383 - connections are isolated. 384 - """ 385 - 386 - def __init__(self, connection, repo, req=None): 387 - """This read-only attribute return a reference to the Connection 388 - object on which the cursor was created. 389 - """ 390 - self.connection = connection 391 - """optionnal issuing request instance""" 392 - self.req = req 393 - self._repo = repo 394 - self._sessid = connection.sessionid 395 - 396 - def close(self): 397 - """no effect""" 398 - pass 399 - 400 - def _txid(self): 401 - return self.connection._txid(self) 402 - 403 - def execute(self, rql, args=None, build_descr=True): 404 - """execute a rql query, return resulting rows and their description in 405 - a :class:`~cubicweb.rset.ResultSet` object 406 - 407 - * `rql` should be a Unicode string or a plain ASCII string, containing 408 - the rql query 409 - 410 - * `args` the optional args dictionary associated to the query, with key 411 - matching named substitution in `rql` 412 - 413 - * `build_descr` is a boolean flag indicating if the description should 414 - be built on select queries (if false, the description will be en empty 415 - list) 416 - 417 - on INSERT queries, there will be one row for each inserted entity, 418 - containing its eid 419 - 420 - on SET queries, XXX describe 421 - 422 - DELETE queries returns no result. 423 - 424 - .. Note:: 425 - to maximize the rql parsing/analyzing cache performance, you should 426 - always use substitute arguments in queries, i.e. avoid query such as:: 427 - 428 - execute('Any X WHERE X eid 123') 429 - 430 - use:: 431 - 432 - execute('Any X WHERE X eid %(x)s', {'x': 123}) 433 - """ 434 - rset = self._repo.execute(self._sessid, rql, args, 435 - build_descr=build_descr, **self._txid()) 436 - rset.req = self.req 437 - return rset 438 - 439 - 440 -class LogCursor(Cursor): 441 - """override the standard cursor to log executed queries""" 442 - 443 - def execute(self, operation, parameters=None, build_descr=True): 444 - """override the standard cursor to log executed queries""" 445 - tstart, cstart = time(), clock() 446 - rset = Cursor.execute(self, operation, parameters, build_descr=build_descr) 447 - self.connection.executed_queries.append((operation, parameters, 448 - time() - tstart, clock() - cstart)) 449 - return rset 450 - 451 -def check_not_closed(func): 452 - def decorator(self, *args, **kwargs): 453 - if self._closed is not None: 454 - raise ProgrammingError('Closed connection %s' % self.sessionid) 455 - return func(self, *args, **kwargs) 456 - return decorator 457 - 458 -class Connection(object): 459 - """DB-API 2.0 compatible Connection object for CubicWeb 460 - """ 461 - # make exceptions available through the connection object 462 - ProgrammingError = ProgrammingError 463 - # attributes that may be overriden per connection instance 464 - cursor_class = Cursor 465 - vreg = None 466 - _closed = None 467 - 468 - def __init__(self, repo, cnxid, cnxprops=None): 469 - self._repo = repo 470 - self.sessionid = cnxid 471 - self._close_on_del = getattr(cnxprops, 'close_on_del', True) 472 - self._web_request = False 473 - if cnxprops and cnxprops.log_queries: 474 - self.executed_queries = [] 475 - self.cursor_class = LogCursor 476 - 477 - @property 478 - def is_repo_in_memory(self): 479 - """return True if this is a local, aka in-memory, connection to the 480 - repository 481 - """ 482 - try: 483 - from cubicweb.server.repository import Repository 484 - except ImportError: 485 - # code not available, no way 486 - return False 487 - return isinstance(self._repo, Repository) 488 - 489 - @property # could be a cached property but we want to prevent assigment to 490 - # catch potential programming error. 491 - def anonymous_connection(self): 492 - login = self._repo.user_info(self.sessionid)[1] 493 - anon_login = self.vreg.config.get('anonymous-user') 494 - return login == anon_login 495 - 496 - def __repr__(self): 497 - if self.anonymous_connection: 498 - return '<Connection %s (anonymous)>' % self.sessionid 499 - return '<Connection %s>' % self.sessionid 500 - 501 - def __enter__(self): 502 - return self.cursor() 503 - 504 - def __exit__(self, exc_type, exc_val, exc_tb): 505 - if exc_type is None: 506 - self.commit() 507 - else: 508 - self.rollback() 509 - return False #propagate the exception 510 - 511 - def __del__(self): 512 - """close the remote connection if necessary""" 513 - if self._closed is None and self._close_on_del: 514 - try: 515 - self.close() 516 - except Exception: 517 - pass 518 - 519 - # server-side service call ################################################# 520 - 521 - @check_not_closed 522 - def call_service(self, regid, **kwargs): 523 - return self._repo.call_service(self.sessionid, regid, **kwargs) 524 - 525 - # connection initialization methods ######################################## 526 - 527 - def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True): 528 - config = self.vreg.config 529 - if cubes is _MARKER: 530 - cubes = self._repo.get_cubes() 531 - elif cubes is None: 532 - cubes = () 533 - else: 534 - if not isinstance(cubes, (list, tuple)): 535 - cubes = (cubes,) 536 - if expand: 537 - cubes = config.expand_cubes(cubes) 538 - if subpath is None: 539 - subpath = esubpath = ('entities', 'views') 540 - else: 541 - esubpath = subpath 542 - if 'views' in subpath: 543 - esubpath = list(subpath) 544 - esubpath.remove('views') 545 - esubpath.append(join('web', 'views')) 546 - # first load available configs, necessary for proper persistent 547 - # properties initialization 548 - config.load_available_configs() 549 - # then init cubes 550 - config.init_cubes(cubes) 551 - # then load appobjects into the registry 552 - vpath = config.build_appobjects_path(reversed(config.cubes_path()), 553 - evobjpath=esubpath, 554 - tvobjpath=subpath) 555 - self.vreg.register_objects(vpath) 556 - 557 - def use_web_compatible_requests(self, baseurl, sitetitle=None): 558 - """monkey patch DBAPIRequest to fake a cw.web.request, so you should 559 - able to call html views using rset from a simple dbapi connection. 560 - 561 - You should call `load_appobjects` at some point to register those views. 562 - """ 563 - DBAPIRequest.property_value = _fake_property_value 564 - DBAPIRequest.next_tabindex = count().next 565 - DBAPIRequest.relative_path = fake 566 - DBAPIRequest.url = fake 567 - DBAPIRequest.get_page_data = fake 568 - DBAPIRequest.set_page_data = fake 569 - # XXX could ask the repo for it's base-url configuration 570 - self.vreg.config.set_option('base-url', baseurl) 571 - self.vreg.config.uiprops = {} 572 - self.vreg.config.datadir_url = baseurl + '/data' 573 - # XXX why is this needed? if really needed, could be fetched by a query 574 - if sitetitle is not None: 575 - self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle} 576 - self._web_request = True 577 - 578 - def request(self): 579 - if self._web_request: 580 - from cubicweb.web.request import DBAPICubicWebRequestBase 581 - req = DBAPICubicWebRequestBase(self.vreg, False) 582 - req.get_header = lambda x, default=None: default 583 - req.set_session = lambda session: DBAPIRequest.set_session( 584 - req, session) 585 - req.relative_path = lambda includeparams=True: '' 586 - else: 587 - req = DBAPIRequest(self.vreg) 588 - req.set_session(DBAPISession(self)) 589 - return req 590 - 591 - @check_not_closed 592 - def user(self, req=None, props=None): 593 - """return the User object associated to this connection""" 594 - # cnx validity is checked by the call to .user_info 595 - eid, login, groups, properties = self._repo.user_info(self.sessionid, 596 - props) 597 - if req is None: 598 - req = self.request() 599 - rset = req.eid_rset(eid, 'CWUser') 600 - if self.vreg is not None and 'etypes' in self.vreg: 601 - user = self.vreg['etypes'].etype_class('CWUser')( 602 - req, rset, row=0, groups=groups, properties=properties) 603 - else: 604 - from cubicweb.entity import Entity 605 - user = Entity(req, rset, row=0) 606 - user.cw_attr_cache['login'] = login # cache login 607 - return user 608 - 609 - @check_not_closed 610 - def check(self): 611 - """raise `BadConnectionId` if the connection is no more valid, else 612 - return its latest activity timestamp. 613 - """ 614 - return self._repo.check_session(self.sessionid) 615 - 616 - def _txid(self, cursor=None): # pylint: disable=E0202 617 - # XXX could now handle various isolation level! 618 - # return a dict as bw compat trick 619 - return {'txid': currentThread().getName()} 620 - 621 - # session data methods ##################################################### 622 - 623 - @check_not_closed 624 - def get_shared_data(self, key, default=None, pop=False, txdata=False): 625 - """return value associated to key in the session's data dictionary or 626 - session's transaction's data if `txdata` is true. 627 - 628 - If pop is True, value will be removed from the dictionary. 629 - 630 - If key isn't defined in the dictionary, value specified by the 631 - `default` argument will be returned. 632 - """ 633 - return self._repo.get_shared_data(self.sessionid, key, default, pop, txdata) 634 - 635 - @check_not_closed 636 - def set_shared_data(self, key, value, txdata=False): 637 - """set value associated to `key` in shared data 638 - 639 - if `txdata` is true, the value will be added to the repository 640 - session's query data which are cleared on commit/rollback of the current 641 - transaction. 642 - """ 643 - return self._repo.set_shared_data(self.sessionid, key, value, txdata) 644 - 645 - # meta-data accessors ###################################################### 646 - 647 - @check_not_closed 648 - def source_defs(self): 649 - """Return the definition of sources used by the repository.""" 650 - return self._repo.source_defs() 651 - 652 - @check_not_closed 653 - def get_schema(self): 654 - """Return the schema currently used by the repository.""" 655 - return self._repo.get_schema() 656 - 657 - @check_not_closed 658 - def get_option_value(self, option, foreid=None): 659 - """Return the value for `option` in the configuration. 660 - 661 - `foreid` argument is deprecated and now useless (as of 3.19). 662 - """ 663 - if foreid is not None: 664 - warn('[3.19] foreid argument is deprecated', DeprecationWarning, 665 - stacklevel=2) 666 - return self._repo.get_option_value(option) 667 - 668 - 669 - @check_not_closed 670 - def entity_metas(self, eid): 671 - """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" 672 - try: 673 - return self._repo.entity_metas(self.sessionid, eid, **self._txid()) 674 - except AttributeError: 675 - # talking to pre 3.19 repository 676 - metas = self._repo.describe(self.sessionid, eid, **self._txid()) 677 - if len(metas) == 3: # even older backward compat 678 - metas = list(metas) 679 - metas.append(metas[1]) 680 - return dict(zip(('type', 'source', 'extid', 'asource'), metas)) 681 - 682 - 683 - @deprecated('[3.19] use .entity_metas(eid) instead') 684 - @check_not_closed 685 - def describe(self, eid, asdict=False): 686 - try: 687 - metas = self._repo.entity_metas(self.sessionid, eid, **self._txid()) 688 - except AttributeError: 689 - metas = self._repo.describe(self.sessionid, eid, **self._txid()) 690 - # talking to pre 3.19 repository 691 - if len(metas) == 3: # even older backward compat 692 - metas = list(metas) 693 - metas.append(metas[1]) 694 - if asdict: 695 - return dict(zip(('type', 'source', 'extid', 'asource'), metas)) 696 - return metas[:-1] 697 - if asdict: 698 - metas['asource'] = meta['source'] # XXX pre 3.19 client compat 699 - return metas 700 - return metas['type'], metas['source'], metas['extid'] 701 - 702 - 703 - # db-api like interface #################################################### 704 - 705 - @check_not_closed 706 - def commit(self): 707 - """Commit pending transaction for this connection to the repository. 708 - 709 - may raises `Unauthorized` or `ValidationError` if we attempted to do 710 - something we're not allowed to for security or integrity reason. 711 - 712 - If the transaction is undoable, a transaction id will be returned. 713 - """ 714 - return self._repo.commit(self.sessionid, **self._txid()) 715 - 716 - @check_not_closed 717 - def rollback(self): 718 - """This method is optional since not all databases provide transaction 719 - support. 720 - 721 - In case a database does provide transactions this method causes the the 722 - database to roll back to the start of any pending transaction. Closing 723 - a connection without committing the changes first will cause an implicit 724 - rollback to be performed. 725 - """ 726 - self._repo.rollback(self.sessionid, **self._txid()) 727 - 728 - @check_not_closed 729 - def cursor(self, req=None): 730 - """Return a new Cursor Object using the connection. 731 - """ 732 - if req is None: 733 - req = self.request() 734 - return self.cursor_class(self, self._repo, req=req) 735 - 736 - @check_not_closed 737 - def close(self): 738 - """Close the connection now (rather than whenever __del__ is called). 739 - 740 - The connection will be unusable from this point forward; an Error (or 741 - subclass) exception will be raised if any operation is attempted with 742 - the connection. The same applies to all cursor objects trying to use the 743 - connection. Note that closing a connection without committing the 744 - changes first will cause an implicit rollback to be performed. 745 - """ 746 - self._repo.close(self.sessionid, **self._txid()) 747 - del self._repo # necessary for proper garbage collection 748 - self._closed = 1 749 - 750 - # undo support ############################################################ 751 - 752 - @check_not_closed 753 - def undoable_transactions(self, ueid=None, req=None, **actionfilters): 754 - """Return a list of undoable transaction objects by the connection's 755 - user, ordered by descendant transaction time. 756 - 757 - Managers may filter according to user (eid) who has done the transaction 758 - using the `ueid` argument. Others will only see their own transactions. 759 - 760 - Additional filtering capabilities is provided by using the following 761 - named arguments: 762 - 763 - * `etype` to get only transactions creating/updating/deleting entities 764 - of the given type 765 - 766 - * `eid` to get only transactions applied to entity of the given eid 767 - 768 - * `action` to get only transactions doing the given action (action in 769 - 'C', 'U', 'D', 'A', 'R'). If `etype`, action can only be 'C', 'U' or 770 - 'D'. 771 - 772 - * `public`: when additional filtering is provided, their are by default 773 - only searched in 'public' actions, unless a `public` argument is given 774 - and set to false. 775 - """ 776 - actionfilters.update(self._txid()) 777 - txinfos = self._repo.undoable_transactions(self.sessionid, ueid, 778 - **actionfilters) 779 - if req is None: 780 - req = self.request() 781 - for txinfo in txinfos: 782 - txinfo.req = req 783 - return txinfos 784 - 785 - @check_not_closed 786 - def transaction_info(self, txuuid, req=None): 787 - """Return transaction object for the given uid. 788 - 789 - raise `NoSuchTransaction` if not found or if session's user is not 790 - allowed (eg not in managers group and the transaction doesn't belong to 791 - him). 792 - """ 793 - txinfo = self._repo.transaction_info(self.sessionid, txuuid, 794 - **self._txid()) 795 - if req is None: 796 - req = self.request() 797 - txinfo.req = req 798 - return txinfo 799 - 800 - @check_not_closed 801 - def transaction_actions(self, txuuid, public=True): 802 - """Return an ordered list of action effectued during that transaction. 803 - 804 - If public is true, return only 'public' actions, eg not ones triggered 805 - under the cover by hooks, else return all actions. 806 - 807 - raise `NoSuchTransaction` if the transaction is not found or if 808 - session's user is not allowed (eg not in managers group and the 809 - transaction doesn't belong to him). 810 - """ 811 - return self._repo.transaction_actions(self.sessionid, txuuid, public, 812 - **self._txid()) 813 - 814 - @check_not_closed 815 - def undo_transaction(self, txuuid): 816 - """Undo the given transaction. Return potential restoration errors. 817 - 818 - raise `NoSuchTransaction` if not found or if session's user is not 819 - allowed (eg not in managers group and the transaction doesn't belong to 820 - him). 821 - """ 822 - return self._repo.undo_transaction(self.sessionid, txuuid, 823 - **self._txid()) 824 - 825 -in_memory_cnx = deprecated('[3.16] use _repo_connect instead)')(_repo_connect)
@@ -392,13 +392,13 @@
826 repo._has_started = True 827 return repo 828 829 def _new_repo(self, config): 830 """Factory method to create a new Repository Instance""" 831 - from cubicweb.dbapi import in_memory_repo 832 + from cubicweb.repoapi import _get_inmemory_repo 833 config._cubes = None 834 - repo = in_memory_repo(config) 835 + repo = _get_inmemory_repo(config) 836 # extending Repository class 837 repo._has_started = False 838 repo._needs_refresh = False 839 repo.turn_repo_on = partial(turn_repo_on, repo) 840 repo.turn_repo_off = partial(turn_repo_off, repo)
@@ -35,5 +35,6 @@
841 "repository-only" instances (i.e. without a http component) are no 842 longer possible. If you have any such instances, you will need to 843 rename the configuration file from repository.conf to all-in-one.conf 844 and run ``cubicweb-ctl upgrade`` to update it. 845 846 +* the old (deprecated since 3.19) `DBAPI` api is completely removed
@@ -1,103 +0,0 @@
847 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 848 -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 849 -# 850 -# This file is part of CubicWeb. 851 -# 852 -# CubicWeb is free software: you can redistribute it and/or modify it under the 853 -# terms of the GNU Lesser General Public License as published by the Free 854 -# Software Foundation, either version 2.1 of the License, or (at your option) 855 -# any later version. 856 -# 857 -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT 858 -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 859 -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 860 -# details. 861 -# 862 -# You should have received a copy of the GNU Lesser General Public License along 863 -# with CubicWeb. If not, see <http://www.gnu.org/licenses/>. 864 -"""unittest for cubicweb.dbapi""" 865 - 866 -from copy import copy 867 - 868 -from logilab.common import tempattr 869 - 870 -from cubicweb import ConnectionError, cwconfig, NoSelectableObject 871 -from cubicweb.dbapi import ProgrammingError, _repo_connect 872 -from cubicweb.devtools.testlib import CubicWebTC 873 - 874 - 875 -class DBAPITC(CubicWebTC): 876 - 877 - def test_public_repo_api(self): 878 - cnx = _repo_connect(self.repo, login='anon', password='anon') 879 - self.assertEqual(cnx.get_schema(), self.repo.schema) 880 - self.assertEqual(cnx.source_defs(), {'system': {'type': 'native', 'uri': 'system', 881 - 'use-cwuri-as-url': False}}) 882 - cnx.close() 883 - self.assertRaises(ProgrammingError, cnx.get_schema) 884 - self.assertRaises(ProgrammingError, cnx.source_defs) 885 - 886 - def test_db_api(self): 887 - cnx = _repo_connect(self.repo, login='anon', password='anon') 888 - self.assertEqual(cnx.rollback(), None) 889 - self.assertEqual(cnx.commit(), None) 890 - cnx.close() 891 - self.assertRaises(ProgrammingError, cnx.rollback) 892 - self.assertRaises(ProgrammingError, cnx.commit) 893 - self.assertRaises(ProgrammingError, cnx.close) 894 - 895 - def test_api(self): 896 - cnx = _repo_connect(self.repo, login='anon', password='anon') 897 - self.assertEqual(cnx.user(None).login, 'anon') 898 - self.assertEqual({'type': u'CWSource', 'source': u'system', 'extid': None}, 899 - cnx.entity_metas(1)) 900 - self.assertEqual(cnx.describe(1), (u'CWSource', u'system', None)) 901 - cnx.close() 902 - self.assertRaises(ProgrammingError, cnx.user, None) 903 - self.assertRaises(ProgrammingError, cnx.entity_metas, 1) 904 - self.assertRaises(ProgrammingError, cnx.describe, 1) 905 - 906 - def test_shared_data_api(self): 907 - cnx = _repo_connect(self.repo, login='anon', password='anon') 908 - self.assertEqual(cnx.get_shared_data('data'), None) 909 - cnx.set_shared_data('data', 4) 910 - self.assertEqual(cnx.get_shared_data('data'), 4) 911 - cnx.get_shared_data('data', pop=True) 912 - cnx.get_shared_data('whatever', pop=True) 913 - self.assertEqual(cnx.get_shared_data('data'), None) 914 - cnx.set_shared_data('data', 4) 915 - self.assertEqual(cnx.get_shared_data('data'), 4) 916 - cnx.close() 917 - self.assertRaises(ProgrammingError, cnx.check) 918 - self.assertRaises(ProgrammingError, cnx.set_shared_data, 'data', 0) 919 - self.assertRaises(ProgrammingError, cnx.get_shared_data, 'data') 920 - 921 - def test_web_compatible_request(self): 922 - config = cwconfig.CubicWebNoAppConfiguration() 923 - cnx = _repo_connect(self.repo, login='admin', password='gingkow') 924 - with tempattr(cnx.vreg, 'config', config): 925 - cnx.use_web_compatible_requests('http://perdu.com') 926 - req = cnx.request() 927 - self.assertEqual(req.base_url(), 'http://perdu.com/') 928 - self.assertEqual(req.from_controller(), 'view') 929 - self.assertEqual(req.relative_path(), '') 930 - req.ajax_replace_url('domid') # don't crash 931 - req.user.cw_adapt_to('IBreadCrumbs') # don't crash 932 - 933 - def test_call_service(self): 934 - ServiceClass = self.vreg['services']['test_service'][0] 935 - for _cw in (self.request(), self.session): 936 - ret_value = _cw.call_service('test_service', msg='coucou') 937 - self.assertEqual('coucou', ServiceClass.passed_here.pop()) 938 - self.assertEqual('babar', ret_value) 939 - with self.login('anon') as ctm: 940 - for _cw in (self.request(), self.session): 941 - with self.assertRaises(NoSelectableObject): 942 - _cw.call_service('test_service', msg='toto') 943 - self.rollback() 944 - self.assertEqual([], ServiceClass.passed_here) 945 - 946 - 947 -if __name__ == '__main__': 948 - from logilab.common.testlib import unittest_main 949 - unittest_main()
@@ -255,22 +255,10 @@
950 'help': 'specify the name server\'s host name. Will be detected by \ 951 broadcast if not provided.', 952 }), 953 ) 954 955 -def config_connect(appid, optconfig): 956 - from cubicweb.dbapi import connect 957 - from getpass import getpass 958 - user = optconfig.user 959 - if not user: 960 - user = raw_input('login: ') 961 - password = optconfig.password 962 - if not password: 963 - password = getpass('password: ') 964 - return connect(login=user, password=password, host=optconfig.host, database=appid) 965 - 966 - 967 ## cwshell helpers ############################################################# 968 969 class AbstractMatcher(object): 970 """Abstract class for CWShellCompleter's matchers. 971
@@ -36,12 +36,12 @@
972 973 from logilab.common.decorators import cached 974 from logilab.common.deprecation import deprecated 975 from logilab.mtconverter import xml_escape 976 977 +from cubicweb import AuthenticationError 978 from cubicweb.req import RequestSessionBase 979 -from cubicweb.dbapi import DBAPIRequest 980 from cubicweb.uilib import remove_html_tags, js 981 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid 982 from cubicweb.view import TRANSITIONAL_DOCTYPE_NOEXT 983 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, 984 RequestError, StatusResponse)
@@ -954,26 +954,33 @@
985 return 986 # 3. site's default language 987 self.set_default_language(vreg) 988 989 990 -class DBAPICubicWebRequestBase(_CubicWebRequestBase, DBAPIRequest): 991 - 992 - def set_session(self, session): 993 - """method called by the session handler when the user is authenticated 994 - or an anonymous connection is open 995 - """ 996 - super(CubicWebRequestBase, self).set_session(session) 997 - # set request language 998 - self.set_user_language(session.user) 999 - 1000 - 1001 def _cnx_func(name): 1002 def proxy(req, *args, **kwargs): 1003 return getattr(req.cnx, name)(*args, **kwargs) 1004 return proxy 1005 1006 +class _NeedAuthAccessMock(object): 1007 + 1008 + def __getattribute__(self, attr): 1009 + raise AuthenticationError() 1010 + 1011 + def __nonzero__(self): 1012 + return False 1013 + 1014 +class _MockAnonymousSession(object): 1015 + sessionid = 'thisisnotarealsession' 1016 + 1017 + @property 1018 + def data(self): 1019 + return {} 1020 + 1021 + @property 1022 + def anonymous_session(self): 1023 + return True 1024 1025 class ConnectionCubicWebRequestBase(_CubicWebRequestBase): 1026 1027 def __init__(self, vreg, https=False, form=None, headers={}): 1028 """"""
@@ -985,12 +992,11 @@
1029 self.translations = vreg.config.translations 1030 except AttributeError: 1031 self.translations = {} 1032 super(ConnectionCubicWebRequestBase, self).__init__(vreg, https=https, 1033 form=form, headers=headers) 1034 - from cubicweb.dbapi import DBAPISession, _NeedAuthAccessMock 1035 - self.session = DBAPISession(None) 1036 + self.session = _MockAnonymousSession() 1037 self.cnx = self.user = _NeedAuthAccessMock() 1038 1039 @property 1040 def transaction_data(self): 1041 return self.cnx.transaction_data
@@ -1005,11 +1011,10 @@
1042 rset = self.cnx.execute(*args, **kwargs) 1043 rset.req = self 1044 return rset 1045 1046 def set_default_language(self, vreg): 1047 - # XXX copy from dbapi 1048 try: 1049 lang = vreg.property_value('ui.language') 1050 except Exception: # property may not be registered 1051 lang = 'en' 1052 try:
@@ -28,11 +28,10 @@
1053 from cubicweb.devtools.testlib import CubicWebTC, real_error_handling 1054 from cubicweb.devtools.fake import FakeRequest 1055 from cubicweb.web import LogOut, Redirect, INTERNAL_FIELD_VALUE 1056 from cubicweb.web.views.basecontrollers import ViewController 1057 from cubicweb.web.application import anonymized_request 1058 -from cubicweb.dbapi import DBAPISession, _NeedAuthAccessMock 1059 from cubicweb import repoapi 1060 1061 class FakeMapping: 1062 """emulates a mapping module""" 1063 def __init__(self):
@@ -366,14 +365,10 @@
1064 sessioncookie = self.app.session_handler.session_cookie(req) 1065 cookie[sessioncookie] = req.session.sessionid 1066 req.set_request_header('Cookie', cookie[sessioncookie].OutputString(), 1067 raw=True) 1068 clear_cache(req, 'get_authorization') 1069 - # reset session as if it was a new incoming request 1070 - req.session = DBAPISession(None) 1071 - req.user = req.cnx = _NeedAuthAccessMock 1072 - 1073 1074 def _test_auth_anon(self, req): 1075 asession = self.app.get_session(req) 1076 # important otherwise _reset_cookie will not use the right session 1077 req.set_cnx(repoapi.ClientConnection(asession))
@@ -1,6 +1,6 @@
1078 -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 1079 +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 1080 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 1081 # 1082 # This file is part of CubicWeb. 1083 # 1084 # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -24,11 +24,10 @@
1085 from logilab.common.decorators import clear_cache 1086 from logilab.common.deprecation import class_renamed 1087 1088 from cubicweb import AuthenticationError, BadConnectionId 1089 from cubicweb.view import Component 1090 -from cubicweb.dbapi import _repo_connect, ConnectionProperties 1091 from cubicweb.web import InvalidSession 1092 from cubicweb.web.application import AbstractAuthenticationManager 1093 1094 class NoAuthInfo(Exception): pass 1095
@@ -25,11 +25,10 @@
1096 1097 from cubicweb import (RepositoryError, Unauthorized, AuthenticationError, 1098 BadConnectionId) 1099 from cubicweb.web import InvalidSession, Redirect 1100 from cubicweb.web.application import AbstractSessionManager 1101 -from cubicweb.dbapi import ProgrammingError, DBAPISession 1102 from cubicweb import repoapi 1103 1104 1105 class InMemoryRepositorySessionManager(AbstractSessionManager): 1106 """manage session data associated to a session identifier"""