1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # |
3 # |
4 # This file is part of CubicWeb. |
4 # This file is part of CubicWeb. |
5 # |
5 # |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
27 """ |
27 """ |
28 from __future__ import print_function |
28 from __future__ import print_function |
29 |
29 |
30 __docformat__ = "restructuredtext en" |
30 __docformat__ = "restructuredtext en" |
31 |
31 |
32 import threading |
|
33 from warnings import warn |
32 from warnings import warn |
34 from itertools import chain |
33 from itertools import chain |
35 from time import time, localtime, strftime |
34 from time import time, localtime, strftime |
36 from contextlib import contextmanager |
35 from contextlib import contextmanager |
37 |
36 |
49 UniqueTogetherError, onevent, ViolatedConstraint) |
48 UniqueTogetherError, onevent, ViolatedConstraint) |
50 from cubicweb import cwvreg, schema, server |
49 from cubicweb import cwvreg, schema, server |
51 from cubicweb.server import ShuttingDown, utils, hook, querier, sources |
50 from cubicweb.server import ShuttingDown, utils, hook, querier, sources |
52 from cubicweb.server.session import Session, InternalManager |
51 from cubicweb.server.session import Session, InternalManager |
53 |
52 |
54 NO_CACHE_RELATIONS = set( [('owned_by', 'object'), |
53 |
55 ('created_by', 'object'), |
54 NO_CACHE_RELATIONS = set([ |
56 ('cw_source', 'object'), |
55 ('owned_by', 'object'), |
57 ]) |
56 ('created_by', 'object'), |
|
57 ('cw_source', 'object'), |
|
58 ]) |
|
59 |
58 |
60 |
59 def prefill_entity_caches(entity): |
61 def prefill_entity_caches(entity): |
60 cnx = entity._cw |
62 cnx = entity._cw |
61 # prefill entity relation caches |
63 # prefill entity relation caches |
62 for rschema in entity.e_schema.subject_relations(): |
64 for rschema in entity.e_schema.subject_relations(): |
71 for rschema in entity.e_schema.object_relations(): |
73 for rschema in entity.e_schema.object_relations(): |
72 rtype = str(rschema) |
74 rtype = str(rschema) |
73 if rtype in schema.VIRTUAL_RTYPES or (rtype, 'object') in NO_CACHE_RELATIONS: |
75 if rtype in schema.VIRTUAL_RTYPES or (rtype, 'object') in NO_CACHE_RELATIONS: |
74 continue |
76 continue |
75 entity.cw_set_relation_cache(rtype, 'object', cnx.empty_rset()) |
77 entity.cw_set_relation_cache(rtype, 'object', cnx.empty_rset()) |
|
78 |
76 |
79 |
77 def del_existing_rel_if_needed(cnx, eidfrom, rtype, eidto): |
80 def del_existing_rel_if_needed(cnx, eidfrom, rtype, eidto): |
78 """delete existing relation when adding a new one if card is 1 or ? |
81 """delete existing relation when adding a new one if card is 1 or ? |
79 |
82 |
80 have to be done once the new relation has been inserted to avoid having |
83 have to be done once the new relation has been inserted to avoid having |
118 relations = [] |
121 relations = [] |
119 activeintegrity = cnx.is_hook_category_activated('activeintegrity') |
122 activeintegrity = cnx.is_hook_category_activated('activeintegrity') |
120 eschema = entity.e_schema |
123 eschema = entity.e_schema |
121 for attr in entity.cw_edited: |
124 for attr in entity.cw_edited: |
122 rschema = eschema.subjrels[attr] |
125 rschema = eschema.subjrels[attr] |
123 if not rschema.final: # inlined relation |
126 if not rschema.final: # inlined relation |
124 value = entity.cw_edited[attr] |
127 value = entity.cw_edited[attr] |
125 relations.append((attr, value)) |
128 relations.append((attr, value)) |
126 cnx.update_rel_cache_add(entity.eid, attr, value) |
129 cnx.update_rel_cache_add(entity.eid, attr, value) |
127 rdef = cnx.rtype_eids_rdef(attr, entity.eid, value) |
130 rdef = cnx.rtype_eids_rdef(attr, entity.eid, value) |
128 if rdef.cardinality[1] in '1?' and activeintegrity: |
131 if rdef.cardinality[1] in '1?' and activeintegrity: |
129 with cnx.security_enabled(read=False): |
132 with cnx.security_enabled(read=False): |
130 cnx.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr, |
133 cnx.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr, |
131 {'x': entity.eid, 'y': value}) |
134 {'x': entity.eid, 'y': value}) |
132 return relations |
135 return relations |
133 |
136 |
134 |
137 |
135 class NullEventBus(object): |
138 class NullEventBus(object): |
136 def publish(self, msg): |
139 def publish(self, msg): |
166 # list of functions to be called at regular interval |
169 # list of functions to be called at regular interval |
167 # list of running threads |
170 # list of running threads |
168 self._running_threads = [] |
171 self._running_threads = [] |
169 # initial schema, should be build or replaced latter |
172 # initial schema, should be build or replaced latter |
170 self.schema = schema.CubicWebSchema(config.appid) |
173 self.schema = schema.CubicWebSchema(config.appid) |
171 self.vreg.schema = self.schema # until actual schema is loaded... |
174 self.vreg.schema = self.schema # until actual schema is loaded... |
172 # shutdown flag |
175 # shutdown flag |
173 self.shutting_down = False |
176 self.shutting_down = False |
174 # sources (additional sources info in the system database) |
177 # sources (additional sources info in the system database) |
175 self.system_source = self.get_source('native', 'system', |
178 self.system_source = self.get_source('native', 'system', |
176 config.system_source_config.copy()) |
179 config.system_source_config.copy()) |
184 # open some connection sets |
187 # open some connection sets |
185 if config.init_cnxset_pool: |
188 if config.init_cnxset_pool: |
186 self.init_cnxset_pool() |
189 self.init_cnxset_pool() |
187 # the hooks manager |
190 # the hooks manager |
188 self.hm = hook.HooksManager(self.vreg) |
191 self.hm = hook.HooksManager(self.vreg) |
|
192 |
189 # registry hook to fix user class on registry reload |
193 # registry hook to fix user class on registry reload |
190 @onevent('after-registry-reload', self) |
194 @onevent('after-registry-reload', self) |
191 def fix_user_classes(self): |
195 def fix_user_classes(self): |
192 # After registry reload the 'CWUser' class used for CWEtype |
196 # After registry reload the 'CWUser' class used for CWEtype |
193 # changed. So any existing user object have a different class than |
197 # changed. So any existing user object have a different class than |
245 if 'CWProperty' in self.schema: |
249 if 'CWProperty' in self.schema: |
246 self.vreg.init_properties(self.properties()) |
250 self.vreg.init_properties(self.properties()) |
247 # 4. close initialization connection set and reopen fresh ones for |
251 # 4. close initialization connection set and reopen fresh ones for |
248 # proper initialization |
252 # proper initialization |
249 self._get_cnxset().close(True) |
253 self._get_cnxset().close(True) |
250 self.cnxsets = [] # list of available cnxsets (can't iterate on a Queue) |
254 # list of available cnxsets (can't iterate on a Queue) |
|
255 self.cnxsets = [] |
251 for i in range(config['connections-pool-size']): |
256 for i in range(config['connections-pool-size']): |
252 self.cnxsets.append(self.system_source.wrapped_connection()) |
257 self.cnxsets.append(self.system_source.wrapped_connection()) |
253 self._cnxsets_pool.put_nowait(self.cnxsets[-1]) |
258 self._cnxsets_pool.put_nowait(self.cnxsets[-1]) |
254 |
259 |
255 # internals ############################################################### |
260 # internals ############################################################### |
256 |
261 |
257 def init_sources_from_database(self): |
262 def init_sources_from_database(self): |
258 self.sources_by_eid = {} |
263 self.sources_by_eid = {} |
259 if self.config.quick_start \ |
264 if self.config.quick_start or 'CWSource' not in self.schema: # 3.10 migration |
260 or not 'CWSource' in self.schema: # # 3.10 migration |
|
261 self.system_source.init_creating() |
265 self.system_source.init_creating() |
262 return |
266 return |
263 with self.internal_cnx() as cnx: |
267 with self.internal_cnx() as cnx: |
264 # FIXME: sources should be ordered (add_entity priority) |
268 # FIXME: sources should be ordered (add_entity priority) |
265 for sourceent in cnx.execute( |
269 for sourceent in cnx.execute( |
266 'Any S, SN, SA, SC WHERE S is_instance_of CWSource, ' |
270 'Any S, SN, SA, SC WHERE S is_instance_of CWSource, ' |
267 'S name SN, S type SA, S config SC').entities(): |
271 'S name SN, S type SA, S config SC').entities(): |
268 if sourceent.name == 'system': |
272 if sourceent.name == 'system': |
269 self.system_source.eid = sourceent.eid |
273 self.system_source.eid = sourceent.eid |
270 self.sources_by_eid[sourceent.eid] = self.system_source |
274 self.sources_by_eid[sourceent.eid] = self.system_source |
271 self.system_source.init(True, sourceent) |
275 self.system_source.init(True, sourceent) |
272 continue |
276 continue |
347 # call instance level initialisation hooks |
351 # call instance level initialisation hooks |
348 self.hm.call_hooks('server_startup', repo=self) |
352 self.hm.call_hooks('server_startup', repo=self) |
349 # register a task to cleanup expired session |
353 # register a task to cleanup expired session |
350 self.cleanup_session_time = self.config['cleanup-session-time'] or 60 * 60 * 24 |
354 self.cleanup_session_time = self.config['cleanup-session-time'] or 60 * 60 * 24 |
351 assert self.cleanup_session_time > 0 |
355 assert self.cleanup_session_time > 0 |
352 cleanup_session_interval = min(60*60, self.cleanup_session_time / 3) |
356 cleanup_session_interval = min(60 * 60, self.cleanup_session_time / 3) |
353 assert self._tasks_manager is not None, "This Repository is not intended to be used as a server" |
357 assert self._tasks_manager is not None, \ |
|
358 "This Repository is not intended to be used as a server" |
354 self._tasks_manager.add_looping_task(cleanup_session_interval, |
359 self._tasks_manager.add_looping_task(cleanup_session_interval, |
355 self.clean_sessions) |
360 self.clean_sessions) |
356 |
361 |
357 def start_looping_tasks(self): |
362 def start_looping_tasks(self): |
358 """Actual "Repository as a server" startup. |
363 """Actual "Repository as a server" startup. |
363 |
368 |
364 XXX Other startup related stuffs are done elsewhere. In Repository |
369 XXX Other startup related stuffs are done elsewhere. In Repository |
365 XXX __init__ or in external codes (various server managers). |
370 XXX __init__ or in external codes (various server managers). |
366 """ |
371 """ |
367 self._prepare_startup() |
372 self._prepare_startup() |
368 assert self._tasks_manager is not None, "This Repository is not intended to be used as a server" |
373 assert self._tasks_manager is not None,\ |
|
374 "This Repository is not intended to be used as a server" |
369 self._tasks_manager.start() |
375 self._tasks_manager.start() |
370 |
376 |
371 def looping_task(self, interval, func, *args): |
377 def looping_task(self, interval, func, *args): |
372 """register a function to be called every `interval` seconds. |
378 """register a function to be called every `interval` seconds. |
373 |
379 |
374 looping tasks can only be registered during repository initialization, |
380 looping tasks can only be registered during repository initialization, |
375 once done this method will fail. |
381 once done this method will fail. |
376 """ |
382 """ |
377 assert self._tasks_manager is not None, "This Repository is not intended to be used as a server" |
383 assert self._tasks_manager is not None,\ |
|
384 "This Repository is not intended to be used as a server" |
378 self._tasks_manager.add_looping_task(interval, func, *args) |
385 self._tasks_manager.add_looping_task(interval, func, *args) |
379 |
386 |
380 def threaded_task(self, func): |
387 def threaded_task(self, func): |
381 """start function in a separated thread""" |
388 """start function in a separated thread""" |
382 utils.RepoThread(func, self._running_threads).start() |
389 utils.RepoThread(func, self._running_threads).start() |
383 |
390 |
384 #@locked |
|
385 def _get_cnxset(self): |
391 def _get_cnxset(self): |
386 try: |
392 try: |
387 return self._cnxsets_pool.get(True, timeout=5) |
393 return self._cnxsets_pool.get(True, timeout=5) |
388 except queue.Empty: |
394 except queue.Empty: |
389 raise Exception('no connections set available after 5 secs, probably either a ' |
395 raise Exception('no connections set available after 5 secs, probably either a ' |
428 self.info('rql st cache hit/miss: %s/%s (%s%% hits)', hits, misses, |
434 self.info('rql st cache hit/miss: %s/%s (%s%% hits)', hits, misses, |
429 (hits * 100) / (hits + misses)) |
435 (hits * 100) / (hits + misses)) |
430 hits, misses = self.system_source.cache_hit, self.system_source.cache_miss |
436 hits, misses = self.system_source.cache_hit, self.system_source.cache_miss |
431 self.info('sql cache hit/miss: %s/%s (%s%% hits)', hits, misses, |
437 self.info('sql cache hit/miss: %s/%s (%s%% hits)', hits, misses, |
432 (hits * 100) / (hits + misses)) |
438 (hits * 100) / (hits + misses)) |
433 nocache = self.system_source.no_cache |
439 nocache = self.system_source.no_cache |
434 self.info('sql cache usage: %s/%s (%s%%)', hits+ misses, nocache, |
440 self.info('sql cache usage: %s/%s (%s%%)', hits + misses, nocache, |
435 ((hits + misses) * 100) / (hits + misses + nocache)) |
441 ((hits + misses) * 100) / (hits + misses + nocache)) |
436 except ZeroDivisionError: |
442 except ZeroDivisionError: |
437 pass |
443 pass |
438 |
444 |
439 def check_auth_info(self, cnx, login, authinfo): |
445 def check_auth_info(self, cnx, login, authinfo): |
456 return associated CWUser instance on success |
462 return associated CWUser instance on success |
457 """ |
463 """ |
458 eid = self.check_auth_info(cnx, login, authinfo) |
464 eid = self.check_auth_info(cnx, login, authinfo) |
459 cwuser = self._build_user(cnx, eid) |
465 cwuser = self._build_user(cnx, eid) |
460 if self.config.consider_user_state and \ |
466 if self.config.consider_user_state and \ |
461 not cwuser.cw_adapt_to('IWorkflowable').state in cwuser.AUTHENTICABLE_STATES: |
467 not cwuser.cw_adapt_to('IWorkflowable').state in cwuser.AUTHENTICABLE_STATES: |
462 raise AuthenticationError('user is not in authenticable state') |
468 raise AuthenticationError('user is not in authenticable state') |
463 return cwuser |
469 return cwuser |
464 |
470 |
465 def _build_user(self, cnx, eid): |
471 def _build_user(self, cnx, eid): |
466 """return a CWUser entity for user with the given eid""" |
472 """return a CWUser entity for user with the given eid""" |
478 return cwuser |
484 return cwuser |
479 |
485 |
480 # public (dbapi) interface ################################################ |
486 # public (dbapi) interface ################################################ |
481 |
487 |
482 @deprecated("[3.19] use _cw.call_service('repo_stats')") |
488 @deprecated("[3.19] use _cw.call_service('repo_stats')") |
483 def stats(self): # XXX restrict to managers session? |
489 def stats(self): # XXX restrict to managers session? |
484 """Return a dictionary containing some statistics about the repository |
490 """Return a dictionary containing some statistics about the repository |
485 resources usage. |
491 resources usage. |
486 |
492 |
487 This is a public method, not requiring a session id. |
493 This is a public method, not requiring a session id. |
488 |
494 |
546 """ |
552 """ |
547 from logilab.common.changelog import Version |
553 from logilab.common.changelog import Version |
548 vcconf = {} |
554 vcconf = {} |
549 with self.internal_cnx() as cnx: |
555 with self.internal_cnx() as cnx: |
550 for pk, version in cnx.execute( |
556 for pk, version in cnx.execute( |
551 'Any K,V WHERE P is CWProperty, P value V, P pkey K, ' |
557 'Any K,V WHERE P is CWProperty, P value V, P pkey K, ' |
552 'P pkey ~="system.version.%"', build_descr=False): |
558 'P pkey ~="system.version.%"', build_descr=False): |
553 cube = pk.split('.')[-1] |
559 cube = pk.split('.')[-1] |
554 # XXX cubicweb migration |
560 # XXX cubicweb migration |
555 if cube in CW_MIGRATION_MAP: |
561 if cube in CW_MIGRATION_MAP: |
556 cube = CW_MIGRATION_MAP[cube] |
562 cube = CW_MIGRATION_MAP[cube] |
557 version = Version(version) |
563 version = Version(version) |
690 |
696 |
691 Beware that unlike the older :meth:`internal_session`, internal |
697 Beware that unlike the older :meth:`internal_session`, internal |
692 connections have all hooks beside security enabled. |
698 connections have all hooks beside security enabled. |
693 """ |
699 """ |
694 with Session(InternalManager(), self).new_cnx() as cnx: |
700 with Session(InternalManager(), self).new_cnx() as cnx: |
695 cnx.user._cw = cnx # XXX remove when "vreg = user._cw.vreg" |
701 cnx.user._cw = cnx # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone |
696 # hack in entity.py is gone |
|
697 with cnx.security_enabled(read=False, write=False): |
702 with cnx.security_enabled(read=False, write=False): |
698 yield cnx |
703 yield cnx |
699 |
704 |
700 def _get_session(self, sessionid, txid=None, checkshuttingdown=True): |
705 def _get_session(self, sessionid, txid=None, checkshuttingdown=True): |
701 """return the session associated with the given session identifier""" |
706 """return the session associated with the given session identifier""" |
730 etcache = self._type_source_cache |
735 etcache = self._type_source_cache |
731 extidcache = self._extid_cache |
736 extidcache = self._extid_cache |
732 rqlcache = self.querier._rql_cache |
737 rqlcache = self.querier._rql_cache |
733 for eid in eids: |
738 for eid in eids: |
734 try: |
739 try: |
735 etype, extid, auri = etcache.pop(int(eid)) # may be a string in some cases |
740 etype, extid, auri = etcache.pop(int(eid)) # may be a string in some cases |
736 rqlcache.pop( ('%s X WHERE X eid %s' % (etype, eid),), None) |
741 rqlcache.pop(('%s X WHERE X eid %s' % (etype, eid),), None) |
737 extidcache.pop(extid, None) |
742 extidcache.pop(extid, None) |
738 except KeyError: |
743 except KeyError: |
739 etype = None |
744 etype = None |
740 rqlcache.pop( ('Any X WHERE X eid %s' % eid,), None) |
745 rqlcache.pop(('Any X WHERE X eid %s' % eid,), None) |
741 self.system_source.clear_eid_cache(eid, etype) |
746 self.system_source.clear_eid_cache(eid, etype) |
742 |
747 |
743 def type_from_eid(self, eid, cnx): |
748 def type_from_eid(self, eid, cnx): |
744 """return the type of the entity with id <eid>""" |
749 """return the type of the entity with id <eid>""" |
745 return self.type_and_source_from_eid(eid, cnx)[0] |
750 return self.type_and_source_from_eid(eid, cnx)[0] |
844 with cnx.security_enabled(read=False, write=False): |
849 with cnx.security_enabled(read=False, write=False): |
845 in_eids = ','.join([str(_e.eid) for _e in entities]) |
850 in_eids = ','.join([str(_e.eid) for _e in entities]) |
846 with cnx.running_hooks_ops(): |
851 with cnx.running_hooks_ops(): |
847 for rschema, _, role in entities[0].e_schema.relation_definitions(): |
852 for rschema, _, role in entities[0].e_schema.relation_definitions(): |
848 if rschema.rule: |
853 if rschema.rule: |
849 continue # computed relation |
854 continue # computed relation |
850 rtype = rschema.type |
855 rtype = rschema.type |
851 if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes: |
856 if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes: |
852 continue |
857 continue |
853 if role == 'subject': |
858 if role == 'subject': |
854 # don't skip inlined relation so they are regularly |
859 # don't skip inlined relation so they are regularly |
859 try: |
864 try: |
860 cnx.execute(rql, build_descr=False) |
865 cnx.execute(rql, build_descr=False) |
861 except ValidationError: |
866 except ValidationError: |
862 raise |
867 raise |
863 except Unauthorized: |
868 except Unauthorized: |
864 self.exception('Unauthorized exception while cascading delete for entity %s. ' |
869 self.exception( |
865 'RQL: %s.\nThis should not happen since security is disabled here.', |
870 'Unauthorized exception while cascading delete for entity %s. ' |
866 entities, rql) |
871 'RQL: %s.\nThis should not happen since security is disabled here.', |
|
872 entities, rql) |
867 raise |
873 raise |
868 except Exception: |
874 except Exception: |
869 if self.config.mode == 'test': |
875 if self.config.mode == 'test': |
870 raise |
876 raise |
871 self.exception('error while cascading delete for entity %s. RQL: %s', |
877 self.exception('error while cascading delete for entity %s. RQL: %s', |
889 |
895 |
890 the entity eid should originally be None and a unique eid is assigned to |
896 the entity eid should originally be None and a unique eid is assigned to |
891 the entity instance |
897 the entity instance |
892 """ |
898 """ |
893 entity = edited.entity |
899 entity = edited.entity |
894 entity._cw_is_saved = False # entity has an eid but is not yet saved |
900 entity._cw_is_saved = False # entity has an eid but is not yet saved |
895 # init edited_attributes before calling before_add_entity hooks |
901 # init edited_attributes before calling before_add_entity hooks |
896 entity.cw_edited = edited |
902 entity.cw_edited = edited |
897 source = self.system_source |
903 source = self.system_source |
898 # allocate an eid to the entity before calling hooks |
904 # allocate an eid to the entity before calling hooks |
899 entity.eid = self.system_source.create_eid(cnx) |
905 entity.eid = self.system_source.create_eid(cnx) |
918 # trigger after_add_entity after after_add_relation |
924 # trigger after_add_entity after after_add_relation |
919 self.hm.call_hooks('after_add_entity', cnx, entity=entity) |
925 self.hm.call_hooks('after_add_entity', cnx, entity=entity) |
920 # call hooks for inlined relations |
926 # call hooks for inlined relations |
921 for attr, value in relations: |
927 for attr, value in relations: |
922 self.hm.call_hooks('before_add_relation', cnx, |
928 self.hm.call_hooks('before_add_relation', cnx, |
923 eidfrom=entity.eid, rtype=attr, eidto=value) |
929 eidfrom=entity.eid, rtype=attr, eidto=value) |
924 self.hm.call_hooks('after_add_relation', cnx, |
930 self.hm.call_hooks('after_add_relation', cnx, |
925 eidfrom=entity.eid, rtype=attr, eidto=value) |
931 eidfrom=entity.eid, rtype=attr, eidto=value) |
926 return entity.eid |
932 return entity.eid |
927 |
933 |
928 def glob_update_entity(self, cnx, edited): |
934 def glob_update_entity(self, cnx, edited): |
929 """replace an entity in the repository |
935 """replace an entity in the repository |
930 the type and the eid of an entity must not be changed |
936 the type and the eid of an entity must not be changed |
952 only_inline_rels = False |
958 only_inline_rels = False |
953 else: |
959 else: |
954 # inlined relation |
960 # inlined relation |
955 previous_value = entity.related(attr) or None |
961 previous_value = entity.related(attr) or None |
956 if previous_value is not None: |
962 if previous_value is not None: |
957 previous_value = previous_value[0][0] # got a result set |
963 previous_value = previous_value[0][0] # got a result set |
958 if previous_value == entity.cw_attr_cache[attr]: |
964 if previous_value == entity.cw_attr_cache[attr]: |
959 previous_value = None |
965 previous_value = None |
960 else: |
966 else: |
961 hm.call_hooks('before_delete_relation', cnx, |
967 hm.call_hooks('before_delete_relation', cnx, |
962 eidfrom=entity.eid, rtype=attr, |
968 eidfrom=entity.eid, rtype=attr, |
994 eidfrom=entity.eid, rtype=attr, eidto=value) |
1000 eidfrom=entity.eid, rtype=attr, eidto=value) |
995 finally: |
1001 finally: |
996 if orig_edited is not None: |
1002 if orig_edited is not None: |
997 entity.cw_edited = orig_edited |
1003 entity.cw_edited = orig_edited |
998 |
1004 |
999 |
|
1000 def glob_delete_entities(self, cnx, eids): |
1005 def glob_delete_entities(self, cnx, eids): |
1001 """delete a list of entities and all related entities from the repository""" |
1006 """delete a list of entities and all related entities from the repository""" |
1002 # mark eids as being deleted in cnx info and setup cache update |
1007 # mark eids as being deleted in cnx info and setup cache update |
1003 # operation (register pending eids before actual deletion to avoid |
1008 # operation (register pending eids before actual deletion to avoid |
1004 # multiple call to glob_delete_entities) |
1009 # multiple call to glob_delete_entities) |
1007 warn('[3.13] eids should be given as a set', DeprecationWarning, |
1012 warn('[3.13] eids should be given as a set', DeprecationWarning, |
1008 stacklevel=2) |
1013 stacklevel=2) |
1009 eids = frozenset(eids) |
1014 eids = frozenset(eids) |
1010 eids = eids - op._container |
1015 eids = eids - op._container |
1011 op._container |= eids |
1016 op._container |= eids |
1012 data_by_etype = {} # values are [list of entities] |
1017 data_by_etype = {} # values are [list of entities] |
1013 # |
1018 # |
1014 # WARNING: the way this dictionary is populated is heavily optimized |
1019 # WARNING: the way this dictionary is populated is heavily optimized |
1015 # and does not use setdefault on purpose. Unless a new release |
1020 # and does not use setdefault on purpose. Unless a new release |
1016 # of the Python interpreter advertises large perf improvements |
1021 # of the Python interpreter advertises large perf improvements |
1017 # in setdefault, this should not be changed without profiling. |
1022 # in setdefault, this should not be changed without profiling. |
1024 except KeyError: |
1029 except KeyError: |
1025 data_by_etype[etype] = [entity] |
1030 data_by_etype[etype] = [entity] |
1026 source = self.system_source |
1031 source = self.system_source |
1027 for etype, entities in data_by_etype.items(): |
1032 for etype, entities in data_by_etype.items(): |
1028 if server.DEBUG & server.DBG_REPO: |
1033 if server.DEBUG & server.DBG_REPO: |
1029 print('DELETE entities', etype, [entity.eid for entity in entities]) |
1034 print('DELETE entities', etype, [e.eid for e in entities]) |
1030 self.hm.call_hooks('before_delete_entity', cnx, entities=entities) |
1035 self.hm.call_hooks('before_delete_entity', cnx, entities=entities) |
1031 self._delete_cascade_multi(cnx, entities) |
1036 self._delete_cascade_multi(cnx, entities) |
1032 source.delete_entities(cnx, entities) |
1037 source.delete_entities(cnx, entities) |
1033 source.delete_info_multi(cnx, entities) |
1038 source.delete_info_multi(cnx, entities) |
1034 self.hm.call_hooks('after_delete_entity', cnx, entities=entities) |
1039 self.hm.call_hooks('after_delete_entity', cnx, entities=entities) |
1109 rschema = self.schema.rschema(rtype) |
1114 rschema = self.schema.rschema(rtype) |
1110 cnx.update_rel_cache_del(subject, rtype, object, rschema.symmetric) |
1115 cnx.update_rel_cache_del(subject, rtype, object, rschema.symmetric) |
1111 self.hm.call_hooks('after_delete_relation', cnx, |
1116 self.hm.call_hooks('after_delete_relation', cnx, |
1112 eidfrom=subject, rtype=rtype, eidto=object) |
1117 eidfrom=subject, rtype=rtype, eidto=object) |
1113 |
1118 |
1114 |
|
1115 |
|
1116 |
|
1117 # these are overridden by set_log_methods below |
1119 # these are overridden by set_log_methods below |
1118 # only defining here to prevent pylint from complaining |
1120 # only defining here to prevent pylint from complaining |
1119 info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None |
1121 info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None |
1120 |
1122 |
|
1123 |
1121 from logging import getLogger |
1124 from logging import getLogger |
1122 from cubicweb import set_log_methods |
1125 from cubicweb import set_log_methods |
1123 set_log_methods(Repository, getLogger('cubicweb.repository')) |
1126 set_log_methods(Repository, getLogger('cubicweb.repository')) |