session.py 25.7 KB
Newer Older
Adrien Di Mascio's avatar
Adrien Di Mascio committed
1
2
3
"""Repository users' and internal' sessions.

:organization: Logilab
4
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
Adrien Di Mascio's avatar
Adrien Di Mascio committed
5
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
6
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
Adrien Di Mascio's avatar
Adrien Di Mascio committed
7
8
9
10
11
12
13
"""
__docformat__ = "restructuredtext en"

import sys
import threading
from time import time

14
from logilab.common.deprecation import deprecated
15
from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
Adrien Di Mascio's avatar
Adrien Di Mascio committed
16
17
from yams import BASE_TYPES

18
from cubicweb import RequestSessionMixIn, Binary, UnknownEid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
19
from cubicweb.dbapi import ConnectionProperties
20
from cubicweb.utils import make_uid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
21
22
from cubicweb.server.rqlrewrite import RQLRewriter

23
ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
24
25
26
27
28
29
30
31
32
33

def is_final(rqlst, variable, args):
    # try to find if this is a final var or not
    for select in rqlst.children:
        for sol in select.solutions:
            etype = variable.get_type(sol, args)
            if etype is None:
                continue
            if etype in BASE_TYPES:
                return True
34
            return False
Adrien Di Mascio's avatar
Adrien Di Mascio committed
35
36
37
38
39
40
41
42
43
44
45
46
47

def _make_description(selected, args, solution):
    """return a description for a result set"""
    description = []
    for term in selected:
        description.append(term.get_type(solution, args))
    return description


class Session(RequestSessionMixIn):
    """tie session id, user, connections pool and other session data all
    together
    """
48

Adrien Di Mascio's avatar
Adrien Di Mascio committed
49
50
51
52
53
54
55
56
57
58
59
    def __init__(self, user, repo, cnxprops=None, _id=None):
        super(Session, self).__init__(repo.vreg)
        self.id = _id or make_uid(user.login.encode('UTF8'))
        cnxprops = cnxprops or ConnectionProperties('inmemory')
        self.user = user
        self.repo = repo
        self.cnxtype = cnxprops.cnxtype
        self.creation = time()
        self.timestamp = self.creation
        self.is_internal_session = False
        self.is_super_session = False
60
        self.default_mode = 'read'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
61
62
63
64
65
66
67
        # short cut to querier .execute method
        self._execute = repo.querier.execute
        # shared data, used to communicate extra information between the client
        # and the rql server
        self.data = {}
        # i18n initialization
        self.set_language(cnxprops.lang)
68
        # internals
Adrien Di Mascio's avatar
Adrien Di Mascio committed
69
        self._threaddata = threading.local()
70
71
        self._threads_in_transaction = set()
        self._closed = False
72

73
74
75
    def __str__(self):
        return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
                                             self.id, id(self))
76

77
78
79
80
    @property
    def schema(self):
        return self.repo.schema

81
82
83
84
85
86
87
88
89
90
    def add_relation(self, fromeid, rtype, toeid):
        if self.is_super_session:
            self.repo.glob_add_relation(self, fromeid, rtype, toeid)
            return
        self.is_super_session = True
        try:
            self.repo.glob_add_relation(self, fromeid, rtype, toeid)
        finally:
            self.is_super_session = False

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    def update_rel_cache_add(self, subject, rtype, object, symetric=False):
        self._update_entity_rel_cache_add(subject, rtype, 'subject', object)
        if symetric:
            self._update_entity_rel_cache_add(object, rtype, 'subject', subject)
        else:
            self._update_entity_rel_cache_add(object, rtype, 'object', subject)

    def update_rel_cache_del(self, subject, rtype, object, symetric=False):
        self._update_entity_rel_cache_del(subject, rtype, 'subject', object)
        if symetric:
            self._update_entity_rel_cache_del(object, rtype, 'object', object)
        else:
            self._update_entity_rel_cache_del(object, rtype, 'object', subject)

    def _rel_cache(self, eid, rtype, role):
        try:
            entity = self.entity_cache(eid)
        except KeyError:
            return
        return entity.relation_cached(rtype, role)

    def _update_entity_rel_cache_add(self, eid, rtype, role, targeteid):
        rcache = self._rel_cache(eid, rtype, role)
        if rcache is not None:
            rset, entities = rcache
            rset.rows.append([targeteid])
117
118
119
            if not isinstance(rset.description, list): # else description not set
                rset.description = list(rset.description)
            rset.description.append([self.describe(targeteid)[0]])
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
            rset.rowcount += 1
            targetentity = self.entity_from_eid(targeteid)
            entities.append(targetentity)

    def _update_entity_rel_cache_del(self, eid, rtype, role, targeteid):
        rcache = self._rel_cache(eid, rtype, role)
        if rcache is not None:
            rset, entities = rcache
            for idx, row in enumerate(rset.rows):
                if row[0] == targeteid:
                    break
            else:
                raise Exception('cache inconsistency for %s %s %s %s' %
                                (eid, rtype, role, targeteid))
            del rset.rows[idx]
            if isinstance(rset.description, list): # else description not set
                del rset.description[idx]
            del entities[idx]
            rset.rowcount -= 1

140
    # resource accessors ######################################################
141

142
143
144
    def actual_session(self):
        """return the original parent session if any, else self"""
        return self
145

146
    def system_sql(self, sql, args=None, rollback_on_failure=True):
147
148
149
        """return a sql cursor on the system database"""
        if not sql.split(None, 1)[0].upper() == 'SELECT':
            self.mode = 'write'
150
151
        return self.pool.source('system').doexec(self, sql, args,
                                                 rollback=rollback_on_failure)
152

Adrien Di Mascio's avatar
Adrien Di Mascio committed
153
154
155
156
157
158
159
160
161
162
163
164
165
    def set_language(self, language):
        """i18n configuration for translation"""
        vreg = self.vreg
        language = language or self.user.property_value('ui.language')
        try:
            self._ = self.__ = vreg.config.translations[language]
        except KeyError:
            language = vreg.property_value('ui.language')
            try:
                self._ = self.__ = vreg.config.translations[language]
            except KeyError:
                self._ = self.__ = unicode
        self.lang = language
166

Adrien Di Mascio's avatar
Adrien Di Mascio committed
167
168
169
170
    def change_property(self, prop, value):
        assert prop == 'lang' # this is the only one changeable property for now
        self.set_language(value)

171
    # connection management ###################################################
Adrien Di Mascio's avatar
Adrien Di Mascio committed
172

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    def keep_pool_mode(self, mode):
        """set pool_mode, e.g. how the session will keep its pool:

        * if mode == 'write', the pool is freed after each ready query, but kept
          until the transaction's end (eg commit or rollback) when a write query
          is detected (eg INSERT/SET/DELETE queries)

        * if mode == 'transaction', the pool is only freed after the
          transaction's end

        notice that a repository has a limited set of pools, and a session has to
        wait for a free pool to run any rql query (unless it already has a pool
        set).
        """
        assert mode in ('transaction', 'write')
        if mode == 'transaction':
            self.default_mode = 'transaction'
        else: # mode == 'write'
            self.default_mode = 'read'

193
    def get_mode(self):
194
        return getattr(self._threaddata, 'mode', self.default_mode)
195
196
197
    def set_mode(self, value):
        self._threaddata.mode = value
    mode = property(get_mode, set_mode,
198
199
                    doc='transaction mode (read/write/transaction), resetted to'
                    ' default_mode on commit / rollback')
200

201
202
203
204
205
    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)
206

207
208
209
210
    @property
    def pool(self):
        """connections pool, set according to transaction mode for each query"""
        return getattr(self._threaddata, 'pool', None)
211

212
    def set_pool(self, checkclosed=True):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
213
        """the session need a pool to execute some queries"""
214
        if checkclosed and self._closed:
215
            raise Exception('try to set pool on a closed session')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
216
        if self.pool is None:
217
            # get pool first to avoid race-condition
218
            self._threaddata.pool = pool = self.repo._get_pool()
219
            try:
220
                pool.pool_set()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
221
222
            except:
                self._threaddata.pool = None
223
                self.repo._free_pool(pool)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
224
                raise
225
            self._threads_in_transaction.add(threading.currentThread())
Adrien Di Mascio's avatar
Adrien Di Mascio committed
226
        return self._threaddata.pool
227

228
    def reset_pool(self, ignoremode=False):
229
        """the session is no longer using its pool, at least for some time"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
230
231
        # pool may be none if no operation has been done since last commit
        # or rollback
232
        if self.pool is not None and (ignoremode or self.mode == 'read'):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
233
            # even in read mode, we must release the current transaction
Sylvain Thénault's avatar
oops    
Sylvain Thénault committed
234
            pool = self.pool
235
236
237
238
            try:
                self._threads_in_transaction.remove(threading.currentThread())
            except KeyError:
                pass
Sylvain Thénault's avatar
Sylvain Thénault committed
239
            pool.pool_reset()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
240
            self._threaddata.pool = None
241
            # free pool once everything is done to avoid race-condition
Sylvain Thénault's avatar
oops    
Sylvain Thénault committed
242
            self.repo._free_pool(pool)
243

244
245
246
247
    def _touch(self):
        """update latest session usage timestamp and reset mode to read"""
        self.timestamp = time()
        self.local_perm_cache.clear()
248
        self._threaddata.mode = self.default_mode
Adrien Di Mascio's avatar
Adrien Di Mascio committed
249
250

    # shared data handling ###################################################
251

Adrien Di Mascio's avatar
Adrien Di Mascio committed
252
253
254
255
256
257
    def get_shared_data(self, key, default=None, pop=False):
        """return value associated to `key` in session data"""
        if pop:
            return self.data.pop(key, default)
        else:
            return self.data.get(key, default)
258

Adrien Di Mascio's avatar
Adrien Di Mascio committed
259
260
261
    def set_shared_data(self, key, value, querydata=False):
        """set value associated to `key` in session data"""
        if querydata:
262
            self.transaction_data[key] = value
Adrien Di Mascio's avatar
Adrien Di Mascio committed
263
264
        else:
            self.data[key] = value
265

Adrien Di Mascio's avatar
Adrien Di Mascio committed
266
    # request interface #######################################################
267

Adrien Di Mascio's avatar
Adrien Di Mascio committed
268
    def set_entity_cache(self, entity):
269
270
271
272
273
274
275
276
277
        # 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
Adrien Di Mascio's avatar
Adrien Di Mascio committed
278
279

    def entity_cache(self, eid):
280
281
282
283
284
285
286
287
288
289
290
291
292
        try:
            return self.transaction_data['ecache'][eid]
        except:
            raise

    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]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
293
294

    def base_url(self):
295
296
297
298
299
300
301
302
        url = self.repo.config['base-url']
        if not url:
            try:
                url = self.repo.config.default_base_url()
            except AttributeError: # default_base_url() might not be available
                self.warning('missing base-url definition in server config')
                url = u''
        return url
303

Adrien Di Mascio's avatar
Adrien Di Mascio committed
304
305
306
307
308
    def from_controller(self):
        """return the id (string) of the controller issuing the request (no
        sense here, always return 'view')
        """
        return 'view'
309

Adrien Di Mascio's avatar
Adrien Di Mascio committed
310
311
312
313
314
315
    def source_defs(self):
        return self.repo.source_defs()

    def describe(self, eid):
        """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
        return self.repo.type_and_source_from_eid(eid, self)
316

Adrien Di Mascio's avatar
Adrien Di Mascio committed
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
    # db-api like interface ###################################################

    def source_from_eid(self, eid):
        """return the source where the entity with id <eid> is located"""
        return self.repo.source_from_eid(eid, self)

    def decorate_rset(self, rset, propagate=False):
        rset.vreg = self.vreg
        rset.req = propagate and self or self.actual_session()
        return rset

    @property
    def super_session(self):
        try:
            csession = self._threaddata.childsession
        except AttributeError:
333
            if isinstance(self, (ChildSession, InternalSession)):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
334
335
336
337
338
                csession = self
            else:
                csession = ChildSession(self)
            self._threaddata.childsession = csession
        # need shared pool set
339
        self.set_pool(checkclosed=False)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
340
        return csession
341

342
    def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
Adrien Di Mascio's avatar
Adrien Di Mascio committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
                       propagate=False):
        """like .execute but with security checking disabled (this method is
        internal to the server, it's not part of the db-api)

        if `propagate` is true, the super_session will be attached to the result
        set instead of the parent session, hence further query done through
        entities fetched from this result set will bypass security as well
        """
        return self.super_session.execute(rql, kwargs, eid_key, build_descr,
                                          propagate)

    @property
    def cursor(self):
        """return a rql cursor"""
        return self
358

Adrien Di Mascio's avatar
Adrien Di Mascio committed
359
360
361
362
363
364
365
366
367
    def execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
                propagate=False):
        """db-api like method directly linked to the querier execute method

        Becare that unlike actual cursor.execute, `build_descr` default to
        false
        """
        rset = self._execute(self, rql, kwargs, eid_key, build_descr)
        return self.decorate_rset(rset, propagate)
368

Adrien Di Mascio's avatar
Adrien Di Mascio committed
369
370
371
372
    def commit(self, reset_pool=True):
        """commit the current session's transaction"""
        if self.pool is None:
            assert not self.pending_operations
373
            self.transaction_data.clear()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
374
            self._touch()
375
            self.debug('commit session %s done (no db activity)', self.id)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
            return
        if self.commit_state:
            return
        # on rollback, an operation should have the following state
        # information:
        # - processed by the precommit/commit event or not
        # - if processed, is it the failed operation
        try:
            for trstate in ('precommit', 'commit'):
                processed = []
                self.commit_state = trstate
                try:
                    while self.pending_operations:
                        operation = self.pending_operations.pop(0)
                        operation.processed = trstate
                        processed.append(operation)
                        operation.handle_event('%s_event' % trstate)
                    self.pending_operations[:] = processed
                    self.debug('%s session %s done', trstate, self.id)
                except:
                    self.exception('error while %sing', trstate)
                    operation.failed = True
                    for operation in processed:
                        operation.handle_event('revert%s_event' % trstate)
                    self.rollback(reset_pool)
                    raise
            self.pool.commit()
        finally:
            self._touch()
            self.commit_state = None
            self.pending_operations[:] = []
407
            self.transaction_data.clear()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
408
            if reset_pool:
409
                self.reset_pool(ignoremode=True)
410

Adrien Di Mascio's avatar
Adrien Di Mascio committed
411
412
413
414
    def rollback(self, reset_pool=True):
        """rollback the current session's transaction"""
        if self.pool is None:
            assert not self.pending_operations
415
            self.transaction_data.clear()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
416
            self._touch()
417
            self.debug('rollback session %s done (no db activity)', self.id)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
418
419
420
421
422
423
424
425
426
427
            return
        try:
            while self.pending_operations:
                try:
                    operation = self.pending_operations.pop(0)
                    operation.handle_event('rollback_event')
                except:
                    self.critical('rollback error', exc_info=sys.exc_info())
                    continue
            self.pool.rollback()
428
            self.debug('rollback for session %s done', self.id)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
429
430
431
        finally:
            self._touch()
            self.pending_operations[:] = []
432
            self.transaction_data.clear()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
433
            if reset_pool:
434
                self.reset_pool(ignoremode=True)
435

Adrien Di Mascio's avatar
Adrien Di Mascio committed
436
437
    def close(self):
        """do not close pool on session close, since they are shared now"""
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
        self._closed = True
        # copy since _threads_in_transaction maybe modified while waiting
        for thread 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 in self._threads_in_transaction):
                    break
            else:
                self.error('thread %s still alive after 10 seconds, will close '
                           'session anyway', thread)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
455
        self.rollback()
456

Adrien Di Mascio's avatar
Adrien Di Mascio committed
457
    # transaction data/operations management ##################################
458

459
460
461
462
463
464
465
    @property
    def transaction_data(self):
        try:
            return self._threaddata.transaction_data
        except AttributeError:
            self._threaddata.transaction_data = {}
            return self._threaddata.transaction_data
466

467
468
469
470
471
472
473
474
    @property
    def pending_operations(self):
        try:
            return self._threaddata.pending_operations
        except AttributeError:
            self._threaddata.pending_operations = []
            return self._threaddata.pending_operations

475

Adrien Di Mascio's avatar
Adrien Di Mascio committed
476
477
478
479
480
481
482
    def add_operation(self, operation, index=None):
        """add an observer"""
        assert self.commit_state != 'commit'
        if index is not None:
            self.pending_operations.insert(index, operation)
        else:
            self.pending_operations.append(operation)
483

Adrien Di Mascio's avatar
Adrien Di Mascio committed
484
    # querier helpers #########################################################
485

486
487
488
489
490
491
492
493
    @property
    def rql_rewriter(self):
        try:
            return self._threaddata._rewriter
        except AttributeError:
            self._threaddata._rewriter = RQLRewriter(self.repo.querier, self)
            return self._threaddata._rewriter

Adrien Di Mascio's avatar
Adrien Di Mascio committed
494
495
496
497
498
499
500
501
502
503
504
505
506
    def build_description(self, rqlst, args, result):
        """build a description for a given result"""
        if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
            # easy, all lines are identical
            selected = rqlst.children[0].selection
            solution = rqlst.children[0].solutions[0]
            description = _make_description(selected, args, solution)
            return [tuple(description)] * len(result)
        # hard, delegate the work :o)
        return self.manual_build_descr(rqlst, args, result)

    def manual_build_descr(self, rqlst, args, result):
        """build a description for a given result by analysing each row
507

Adrien Di Mascio's avatar
Adrien Di Mascio committed
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
        XXX could probably be done more efficiently during execution of query
        """
        # not so easy, looks for variable which changes from one solution
        # to another
        unstables = rqlst.get_variable_variables()
        basedescription = []
        todetermine = []
        selected = rqlst.children[0].selection # sample selection
        for i, term in enumerate(selected):
            if isinstance(term, Function) and term.descr().rtype is not None:
                basedescription.append(term.get_type(term.descr().rtype, args))
                continue
            for vref in term.get_nodes(VariableRef):
                if vref.name in unstables:
                    basedescription.append(None)
                    todetermine.append( (i, is_final(rqlst, vref.variable, args)) )
                    break
            else:
                # sample etype
                etype = rqlst.children[0].solutions[0]
                basedescription.append(term.get_type(etype, args))
        if not todetermine:
            return [tuple(basedescription)] * len(result)
        return self._build_descr(result, basedescription, todetermine)
532

Adrien Di Mascio's avatar
Adrien Di Mascio committed
533
534
535
536
537
538
539
540
541
542
543
    def _build_descr(self, result, basedescription, todetermine):
        description = []
        etype_from_eid = self.describe
        for row in result:
            row_descr = basedescription
            for index, isfinal in todetermine:
                value = row[index]
                if value is None:
                    # None value inserted by an outer join, no type
                    row_descr[index] = None
                    continue
544
545
546
547
                if isfinal:
                    row_descr[index] = etype_from_pyobj(value)
                else:
                    try:
548
                        row_descr[index] = etype_from_eid(value)[0]
549
550
551
                    except UnknownEid:
                        self.critical('wrong eid %s in repository, should check database' % value)
                        row_descr[index] = row[index] = None
Adrien Di Mascio's avatar
Adrien Di Mascio committed
552
553
554
            description.append(tuple(row_descr))
        return description

555
556
557
558
559
    @deprecated("use vreg['etypes'].etype_class(etype)")
    def etype_class(self, etype):
        """return an entity class for the given entity type"""
        return self.vreg['etypes'].etype_class(etype)

560
    @deprecated('use direct access to session.transaction_data')
561
562
563
564
565
566
567
568
569
    def query_data(self, key, default=None, setdefault=False, pop=False):
        if setdefault:
            assert not pop
            return self.transaction_data.setdefault(key, default)
        if pop:
            return self.transaction_data.pop(key, default)
        else:
            return self.transaction_data.get(key, default)

570
    @deprecated('use entity_from_eid(eid, etype=None)')
571
572
    def entity(self, eid):
        """return a result set for the given eid"""
573
        return self.entity_from_eid(eid)
574

575

Adrien Di Mascio's avatar
Adrien Di Mascio committed
576
577
578
579
class ChildSession(Session):
    """child (or internal) session are used to hijack the security system
    """
    cnxtype = 'inmemory'
580

Adrien Di Mascio's avatar
Adrien Di Mascio committed
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
    def __init__(self, parent_session):
        self.id = None
        self.is_internal_session = False
        self.is_super_session = True
        # session which has created this one
        self.parent_session = parent_session
        self.user = InternalManager()
        self.repo = parent_session.repo
        self.vreg = parent_session.vreg
        self.data = parent_session.data
        self.encoding = parent_session.encoding
        self.lang = parent_session.lang
        self._ = self.__ = parent_session._
        # short cut to querier .execute method
        self._execute = self.repo.querier.execute
596

Adrien Di Mascio's avatar
Adrien Di Mascio committed
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
    @property
    def super_session(self):
        return self

    def get_mode(self):
        return self.parent_session.mode
    def set_mode(self, value):
        self.parent_session.set_mode(value)
    mode = property(get_mode, set_mode)

    def get_commit_state(self):
        return self.parent_session.commit_state
    def set_commit_state(self, value):
        self.parent_session.set_commit_state(value)
    commit_state = property(get_commit_state, set_commit_state)
612

Adrien Di Mascio's avatar
Adrien Di Mascio committed
613
614
615
616
617
618
619
    @property
    def pool(self):
        return self.parent_session.pool
    @property
    def pending_operations(self):
        return self.parent_session.pending_operations
    @property
620
621
    def transaction_data(self):
        return self.parent_session.transaction_data
622

Adrien Di Mascio's avatar
Adrien Di Mascio committed
623
624
625
    def set_pool(self):
        """the session need a pool to execute some queries"""
        self.parent_session.set_pool()
626

Adrien Di Mascio's avatar
Adrien Di Mascio committed
627
628
629
630
631
632
633
634
    def reset_pool(self):
        """the session has no longer using its pool, at least for some time
        """
        self.parent_session.reset_pool()

    def actual_session(self):
        """return the original parent session if any, else self"""
        return self.parent_session
635

Adrien Di Mascio's avatar
Adrien Di Mascio committed
636
637
638
    def commit(self, reset_pool=True):
        """commit the current session's transaction"""
        self.parent_session.commit(reset_pool)
639

Adrien Di Mascio's avatar
Adrien Di Mascio committed
640
641
642
    def rollback(self, reset_pool=True):
        """rollback the current session's transaction"""
        self.parent_session.rollback(reset_pool)
643

Adrien Di Mascio's avatar
Adrien Di Mascio committed
644
645
646
    def close(self):
        """do not close pool on session close, since they are shared now"""
        self.rollback()
647

Adrien Di Mascio's avatar
Adrien Di Mascio committed
648
649
650
651
652
653
654
    def user_data(self):
        """returns a dictionnary with this user's information"""
        return self.parent_session.user_data()


class InternalSession(Session):
    """special session created internaly by the repository"""
655

Adrien Di Mascio's avatar
Adrien Di Mascio committed
656
657
658
659
660
661
    def __init__(self, repo, cnxprops=None):
        super(InternalSession, self).__init__(_IMANAGER, repo, cnxprops,
                                              _id='internal')
        self.cnxtype = 'inmemory'
        self.is_internal_session = True
        self.is_super_session = True
662

Adrien Di Mascio's avatar
Adrien Di Mascio committed
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
    @property
    def super_session(self):
        return self


class InternalManager(object):
    """a manager user with all access rights used internally for task such as
    bootstrapping the repository or creating regular users according to
    repository content
    """
    def __init__(self):
        self.eid = -1
        self.login = u'__internal_manager__'
        self.properties = {}

    def matching_groups(self, groups):
        return 1

    def is_in_group(self, group):
        return True

    def owns(self, eid):
        return True
686

Adrien Di Mascio's avatar
Adrien Di Mascio committed
687
688
689
690
691
692
693
694
    def has_permission(self, pname, contexteid=None):
        return True

    def property_value(self, key):
        if key == 'ui.language':
            return 'en'
        return None

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
695
_IMANAGER = InternalManager()
Adrien Di Mascio's avatar
Adrien Di Mascio committed
696
697
698
699

from logging import getLogger
from cubicweb import set_log_methods
set_log_methods(Session, getLogger('cubicweb.session'))