29 |
29 |
30 from logilab.common.deprecation import deprecated |
30 from logilab.common.deprecation import deprecated |
31 from logilab.common.textutils import unormalize |
31 from logilab.common.textutils import unormalize |
32 from logilab.common.registry import objectify_predicate |
32 from logilab.common.registry import objectify_predicate |
33 |
33 |
34 from cubicweb import UnknownEid, QueryError, schema, server |
34 from cubicweb import UnknownEid, QueryError, schema, server, ProgrammingError |
35 from cubicweb.req import RequestSessionBase |
35 from cubicweb.req import RequestSessionBase |
36 from cubicweb.utils import make_uid |
36 from cubicweb.utils import make_uid |
37 from cubicweb.rqlrewrite import RQLRewriter |
37 from cubicweb.rqlrewrite import RQLRewriter |
38 from cubicweb.server import ShuttingDown |
38 from cubicweb.server import ShuttingDown |
39 from cubicweb.server.edition import EditedEntity |
39 from cubicweb.server.edition import EditedEntity |
369 def wrapper(cnx, *args, **kwargs): |
369 def wrapper(cnx, *args, **kwargs): |
370 with cnx.ensure_cnx_set: |
370 with cnx.ensure_cnx_set: |
371 return func(cnx, *args, **kwargs) |
371 return func(cnx, *args, **kwargs) |
372 return wrapper |
372 return wrapper |
373 |
373 |
|
374 def _open_only(func): |
|
375 """decorator for Connection method that check it is open""" |
|
376 @functools.wraps(func) |
|
377 def check_open(cnx, *args, **kwargs): |
|
378 if not cnx._open: |
|
379 raise ProgrammingError('Closed Connection: %s' |
|
380 % cnx.connectionid) |
|
381 return func(cnx, *args, **kwargs) |
|
382 return check_open |
374 |
383 |
375 class Connection(RequestSessionBase): |
384 class Connection(RequestSessionBase): |
376 """Repository Connection |
385 """Repository Connection |
377 |
386 |
378 Holds all connection related data |
387 Holds all connection related data |
564 self.local_perm_cache.clear() |
575 self.local_perm_cache.clear() |
565 self.rewriter = RQLRewriter(self) |
576 self.rewriter = RQLRewriter(self) |
566 |
577 |
567 # Connection Set Management ############################################### |
578 # Connection Set Management ############################################### |
568 @property |
579 @property |
|
580 @_open_only |
569 def cnxset(self): |
581 def cnxset(self): |
570 return self._cnxset |
582 return self._cnxset |
571 |
583 |
572 @cnxset.setter |
584 @cnxset.setter |
|
585 @_open_only |
573 def cnxset(self, new_cnxset): |
586 def cnxset(self, new_cnxset): |
574 with self._cnxset_tracker: |
587 with self._cnxset_tracker: |
575 old_cnxset = self._cnxset |
588 old_cnxset = self._cnxset |
576 if new_cnxset is old_cnxset: |
589 if new_cnxset is old_cnxset: |
577 return #nothing to do |
590 return #nothing to do |
646 # an acceptable risk. Anyway we could activate it or not according to a |
662 # an acceptable risk. Anyway we could activate it or not according to a |
647 # configuration option |
663 # configuration option |
648 |
664 |
649 def set_entity_cache(self, entity): |
665 def set_entity_cache(self, entity): |
650 """Add `entity` to the connection entity cache""" |
666 """Add `entity` to the connection entity cache""" |
|
667 #XXX not using _open_only because before at creation time. _set_user |
|
668 # call this function to cache the Connection user. |
|
669 if entity.cw_etype != 'CWUser' and not self._open: |
|
670 raise ProgrammingError('Closed Connection: %s' |
|
671 % self.connectionid) |
651 ecache = self.transaction_data.setdefault('ecache', {}) |
672 ecache = self.transaction_data.setdefault('ecache', {}) |
652 ecache.setdefault(entity.eid, entity) |
673 ecache.setdefault(entity.eid, entity) |
653 |
674 |
|
675 @_open_only |
654 def entity_cache(self, eid): |
676 def entity_cache(self, eid): |
655 """get cache entity for `eid`""" |
677 """get cache entity for `eid`""" |
656 return self.transaction_data['ecache'][eid] |
678 return self.transaction_data['ecache'][eid] |
657 |
679 |
|
680 @_open_only |
658 def cached_entities(self): |
681 def cached_entities(self): |
659 """return the whole entity cache""" |
682 """return the whole entity cache""" |
660 return self.transaction_data.get('ecache', {}).values() |
683 return self.transaction_data.get('ecache', {}).values() |
661 |
684 |
|
685 @_open_only |
662 def drop_entity_cache(self, eid=None): |
686 def drop_entity_cache(self, eid=None): |
663 """drop entity from the cache |
687 """drop entity from the cache |
664 |
688 |
665 If eid is None, the whole cache is dropped""" |
689 If eid is None, the whole cache is dropped""" |
666 if eid is None: |
690 if eid is None: |
681 You may use this in hooks when you know both eids of the relation you |
706 You may use this in hooks when you know both eids of the relation you |
682 want to add. |
707 want to add. |
683 """ |
708 """ |
684 self.add_relations([(rtype, [(fromeid, toeid)])]) |
709 self.add_relations([(rtype, [(fromeid, toeid)])]) |
685 |
710 |
|
711 @_open_only |
686 def add_relations(self, relations): |
712 def add_relations(self, relations): |
687 '''set many relation using a shortcut similar to the one in add_relation |
713 '''set many relation using a shortcut similar to the one in add_relation |
688 |
714 |
689 relations is a list of 2-uples, the first element of each |
715 relations is a list of 2-uples, the first element of each |
690 2-uple is the rtype, and the second is a list of (fromeid, |
716 2-uple is the rtype, and the second is a list of (fromeid, |
708 self.repo.glob_add_relations(self, relations_dict) |
734 self.repo.glob_add_relations(self, relations_dict) |
709 for edited in edited_entities.itervalues(): |
735 for edited in edited_entities.itervalues(): |
710 self.repo.glob_update_entity(self, edited) |
736 self.repo.glob_update_entity(self, edited) |
711 |
737 |
712 |
738 |
|
739 @_open_only |
713 def delete_relation(self, fromeid, rtype, toeid): |
740 def delete_relation(self, fromeid, rtype, toeid): |
714 """provide direct access to the repository method to delete a relation. |
741 """provide direct access to the repository method to delete a relation. |
715 |
742 |
716 This is equivalent to the following rql query: |
743 This is equivalent to the following rql query: |
717 |
744 |
729 else: |
756 else: |
730 self.repo.glob_delete_relation(self, fromeid, rtype, toeid) |
757 self.repo.glob_delete_relation(self, fromeid, rtype, toeid) |
731 |
758 |
732 # relations cache handling ################################################# |
759 # relations cache handling ################################################# |
733 |
760 |
|
761 @_open_only |
734 def update_rel_cache_add(self, subject, rtype, object, symmetric=False): |
762 def update_rel_cache_add(self, subject, rtype, object, symmetric=False): |
735 self._update_entity_rel_cache_add(subject, rtype, 'subject', object) |
763 self._update_entity_rel_cache_add(subject, rtype, 'subject', object) |
736 if symmetric: |
764 if symmetric: |
737 self._update_entity_rel_cache_add(object, rtype, 'subject', subject) |
765 self._update_entity_rel_cache_add(object, rtype, 'subject', subject) |
738 else: |
766 else: |
739 self._update_entity_rel_cache_add(object, rtype, 'object', subject) |
767 self._update_entity_rel_cache_add(object, rtype, 'object', subject) |
740 |
768 |
|
769 @_open_only |
741 def update_rel_cache_del(self, subject, rtype, object, symmetric=False): |
770 def update_rel_cache_del(self, subject, rtype, object, symmetric=False): |
742 self._update_entity_rel_cache_del(subject, rtype, 'subject', object) |
771 self._update_entity_rel_cache_del(subject, rtype, 'subject', object) |
743 if symmetric: |
772 if symmetric: |
744 self._update_entity_rel_cache_del(object, rtype, 'object', object) |
773 self._update_entity_rel_cache_del(object, rtype, 'object', object) |
745 else: |
774 else: |
746 self._update_entity_rel_cache_del(object, rtype, 'object', subject) |
775 self._update_entity_rel_cache_del(object, rtype, 'object', subject) |
747 |
776 |
|
777 @_open_only |
748 def _update_entity_rel_cache_add(self, eid, rtype, role, targeteid): |
778 def _update_entity_rel_cache_add(self, eid, rtype, role, targeteid): |
749 try: |
779 try: |
750 entity = self.entity_cache(eid) |
780 entity = self.entity_cache(eid) |
751 except KeyError: |
781 except KeyError: |
752 return |
782 return |
798 |
829 |
799 # Tracking of entity added of removed in the transaction ################## |
830 # Tracking of entity added of removed in the transaction ################## |
800 # |
831 # |
801 # Those are function to allows cheap call from client in other process. |
832 # Those are function to allows cheap call from client in other process. |
802 |
833 |
|
834 @_open_only |
803 def deleted_in_transaction(self, eid): |
835 def deleted_in_transaction(self, eid): |
804 """return True if the entity of the given eid is being deleted in the |
836 """return True if the entity of the given eid is being deleted in the |
805 current transaction |
837 current transaction |
806 """ |
838 """ |
807 return eid in self.transaction_data.get('pendingeids', ()) |
839 return eid in self.transaction_data.get('pendingeids', ()) |
808 |
840 |
|
841 @_open_only |
809 def added_in_transaction(self, eid): |
842 def added_in_transaction(self, eid): |
810 """return True if the entity of the given eid is being created in the |
843 """return True if the entity of the given eid is being created in the |
811 current transaction |
844 current transaction |
812 """ |
845 """ |
813 return eid in self.transaction_data.get('neweids', ()) |
846 return eid in self.transaction_data.get('neweids', ()) |
814 |
847 |
815 # Operation management #################################################### |
848 # Operation management #################################################### |
816 |
849 |
|
850 @_open_only |
817 def add_operation(self, operation, index=None): |
851 def add_operation(self, operation, index=None): |
818 """add an operation to be executed at the end of the transaction""" |
852 """add an operation to be executed at the end of the transaction""" |
819 if index is None: |
853 if index is None: |
820 self.pending_operations.append(operation) |
854 self.pending_operations.append(operation) |
821 else: |
855 else: |
822 self.pending_operations.insert(index, operation) |
856 self.pending_operations.insert(index, operation) |
823 |
857 |
824 # Hooks control ########################################################### |
858 # Hooks control ########################################################### |
825 |
859 |
|
860 @_open_only |
826 def allow_all_hooks_but(self, *categories): |
861 def allow_all_hooks_but(self, *categories): |
827 return _hooks_control(self, HOOKS_ALLOW_ALL, *categories) |
862 return _hooks_control(self, HOOKS_ALLOW_ALL, *categories) |
828 |
863 |
|
864 @_open_only |
829 def deny_all_hooks_but(self, *categories): |
865 def deny_all_hooks_but(self, *categories): |
830 return _hooks_control(self, HOOKS_DENY_ALL, *categories) |
866 return _hooks_control(self, HOOKS_DENY_ALL, *categories) |
831 |
867 |
|
868 @_open_only |
832 def disable_hook_categories(self, *categories): |
869 def disable_hook_categories(self, *categories): |
833 """disable the given hook categories: |
870 """disable the given hook categories: |
834 |
871 |
835 - on HOOKS_DENY_ALL mode, ensure those categories are not enabled |
872 - on HOOKS_DENY_ALL mode, ensure those categories are not enabled |
836 - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled |
873 - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled |
846 disabledcats = self.disabled_hook_cats |
883 disabledcats = self.disabled_hook_cats |
847 changes = categories - disabledcats |
884 changes = categories - disabledcats |
848 disabledcats |= changes # changes is small hence faster |
885 disabledcats |= changes # changes is small hence faster |
849 return tuple(changes) |
886 return tuple(changes) |
850 |
887 |
|
888 @_open_only |
851 def enable_hook_categories(self, *categories): |
889 def enable_hook_categories(self, *categories): |
852 """enable the given hook categories: |
890 """enable the given hook categories: |
853 |
891 |
854 - on HOOKS_DENY_ALL mode, ensure those categories are enabled |
892 - on HOOKS_DENY_ALL mode, ensure those categories are enabled |
855 - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled |
893 - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled |
865 disabledcats = self.disabled_hook_cats |
903 disabledcats = self.disabled_hook_cats |
866 changes = disabledcats & categories |
904 changes = disabledcats & categories |
867 disabledcats -= changes # changes is small hence faster |
905 disabledcats -= changes # changes is small hence faster |
868 return tuple(changes) |
906 return tuple(changes) |
869 |
907 |
|
908 @_open_only |
870 def is_hook_category_activated(self, category): |
909 def is_hook_category_activated(self, category): |
871 """return a boolean telling if the given category is currently activated |
910 """return a boolean telling if the given category is currently activated |
872 or not |
911 or not |
873 """ |
912 """ |
874 if self.hooks_mode is HOOKS_DENY_ALL: |
913 if self.hooks_mode is HOOKS_DENY_ALL: |
875 return category in self.enabled_hook_cats |
914 return category in self.enabled_hook_cats |
876 return category not in self.disabled_hook_cats |
915 return category not in self.disabled_hook_cats |
877 |
916 |
|
917 @_open_only |
878 def is_hook_activated(self, hook): |
918 def is_hook_activated(self, hook): |
879 """return a boolean telling if the given hook class is currently |
919 """return a boolean telling if the given hook class is currently |
880 activated or not |
920 activated or not |
881 """ |
921 """ |
882 return self.is_hook_category_activated(hook.category) |
922 return self.is_hook_category_activated(hook.category) |
883 |
923 |
884 # Security management ##################################################### |
924 # Security management ##################################################### |
885 |
925 |
|
926 @_open_only |
886 def security_enabled(self, read=None, write=None): |
927 def security_enabled(self, read=None, write=None): |
887 return _security_enabled(self, read=read, write=write) |
928 return _security_enabled(self, read=read, write=write) |
888 |
929 |
889 @property |
930 @property |
|
931 @_open_only |
890 def read_security(self): |
932 def read_security(self): |
891 return self._read_security |
933 return self._read_security |
892 |
934 |
893 @read_security.setter |
935 @read_security.setter |
|
936 @_open_only |
894 def read_security(self, activated): |
937 def read_security(self, activated): |
895 oldmode = self._read_security |
938 oldmode = self._read_security |
896 self._read_security = activated |
939 self._read_security = activated |
897 # running_dbapi_query used to detect hooks triggered by a 'dbapi' query |
940 # running_dbapi_query used to detect hooks triggered by a 'dbapi' query |
898 # (eg not issued on the session). This is tricky since we the execution |
941 # (eg not issued on the session). This is tricky since we the execution |
914 self.running_dbapi_query = (oldmode is DEFAULT_SECURITY |
957 self.running_dbapi_query = (oldmode is DEFAULT_SECURITY |
915 or activated is DEFAULT_SECURITY) |
958 or activated is DEFAULT_SECURITY) |
916 |
959 |
917 # undo support ############################################################ |
960 # undo support ############################################################ |
918 |
961 |
|
962 @_open_only |
919 def ertype_supports_undo(self, ertype): |
963 def ertype_supports_undo(self, ertype): |
920 return self.undo_actions and ertype not in NO_UNDO_TYPES |
964 return self.undo_actions and ertype not in NO_UNDO_TYPES |
921 |
965 |
|
966 @_open_only |
922 def transaction_uuid(self, set=True): |
967 def transaction_uuid(self, set=True): |
923 uuid = self.transaction_data.get('tx_uuid') |
968 uuid = self.transaction_data.get('tx_uuid') |
924 if set and uuid is None: |
969 if set and uuid is None: |
925 self.transaction_data['tx_uuid'] = uuid = uuid4().hex |
970 self.transaction_data['tx_uuid'] = uuid = uuid4().hex |
926 self.repo.system_source.start_undoable_transaction(self, uuid) |
971 self.repo.system_source.start_undoable_transaction(self, uuid) |
927 return uuid |
972 return uuid |
928 |
973 |
|
974 @_open_only |
929 def transaction_inc_action_counter(self): |
975 def transaction_inc_action_counter(self): |
930 num = self.transaction_data.setdefault('tx_action_count', 0) + 1 |
976 num = self.transaction_data.setdefault('tx_action_count', 0) + 1 |
931 self.transaction_data['tx_action_count'] = num |
977 self.transaction_data['tx_action_count'] = num |
932 return num |
978 return num |
933 # db-api like interface ################################################### |
979 # db-api like interface ################################################### |
934 |
980 |
|
981 @_open_only |
935 def source_defs(self): |
982 def source_defs(self): |
936 return self.repo.source_defs() |
983 return self.repo.source_defs() |
937 |
984 |
938 @_with_cnx_set |
985 @_with_cnx_set |
|
986 @_open_only |
939 def describe(self, eid, asdict=False): |
987 def describe(self, eid, asdict=False): |
940 """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" |
988 """return a tuple (type, sourceuri, extid) for the entity with id <eid>""" |
941 metas = self.repo.type_and_source_from_eid(eid, self) |
989 metas = self.repo.type_and_source_from_eid(eid, self) |
942 if asdict: |
990 if asdict: |
943 return dict(zip(('type', 'source', 'extid', 'asource'), metas)) |
991 return dict(zip(('type', 'source', 'extid', 'asource'), metas)) |
944 # XXX :-1 for cw compat, use asdict=True for full information |
992 # XXX :-1 for cw compat, use asdict=True for full information |
945 return metas[:-1] |
993 return metas[:-1] |
946 |
994 |
947 @_with_cnx_set |
995 @_with_cnx_set |
|
996 @_open_only |
948 def source_from_eid(self, eid): |
997 def source_from_eid(self, eid): |
949 """return the source where the entity with id <eid> is located""" |
998 """return the source where the entity with id <eid> is located""" |
950 return self.repo.source_from_eid(eid, self) |
999 return self.repo.source_from_eid(eid, self) |
951 |
1000 |
952 # core method ############################################################# |
1001 # core method ############################################################# |
953 |
1002 |
954 @_with_cnx_set |
1003 @_with_cnx_set |
|
1004 @_open_only |
955 def execute(self, rql, kwargs=None, eid_key=None, build_descr=True): |
1005 def execute(self, rql, kwargs=None, eid_key=None, build_descr=True): |
956 """db-api like method directly linked to the querier execute method. |
1006 """db-api like method directly linked to the querier execute method. |
957 |
1007 |
958 See :meth:`cubicweb.dbapi.Cursor.execute` documentation. |
1008 See :meth:`cubicweb.dbapi.Cursor.execute` documentation. |
959 """ |
1009 """ |
964 rset = self._execute(self, rql, kwargs, build_descr) |
1014 rset = self._execute(self, rql, kwargs, build_descr) |
965 rset.req = self |
1015 rset.req = self |
966 self._session_timestamp.touch() |
1016 self._session_timestamp.touch() |
967 return rset |
1017 return rset |
968 |
1018 |
|
1019 @_open_only |
969 def rollback(self, free_cnxset=True, reset_pool=None): |
1020 def rollback(self, free_cnxset=True, reset_pool=None): |
970 """rollback the current transaction""" |
1021 """rollback the current transaction""" |
971 if reset_pool is not None: |
1022 if reset_pool is not None: |
972 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1023 warn('[3.13] use free_cnxset argument instead for reset_pool', |
973 DeprecationWarning, stacklevel=2) |
1024 DeprecationWarning, stacklevel=2) |
994 self._session_timestamp.touch() |
1045 self._session_timestamp.touch() |
995 if free_cnxset: |
1046 if free_cnxset: |
996 self.free_cnxset(ignoremode=True) |
1047 self.free_cnxset(ignoremode=True) |
997 self.clear() |
1048 self.clear() |
998 |
1049 |
|
1050 @_open_only |
999 def commit(self, free_cnxset=True, reset_pool=None): |
1051 def commit(self, free_cnxset=True, reset_pool=None): |
1000 """commit the current session's transaction""" |
1052 """commit the current session's transaction""" |
1001 if reset_pool is not None: |
1053 if reset_pool is not None: |
1002 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1054 warn('[3.13] use free_cnxset argument instead for reset_pool', |
1003 DeprecationWarning, stacklevel=2) |
1055 DeprecationWarning, stacklevel=2) |
1114 raise |
1168 raise |
1115 source.warning("trying to reconnect") |
1169 source.warning("trying to reconnect") |
1116 self.cnxset.reconnect(source) |
1170 self.cnxset.reconnect(source) |
1117 return source.doexec(self, sql, args, rollback=rollback_on_failure) |
1171 return source.doexec(self, sql, args, rollback=rollback_on_failure) |
1118 |
1172 |
|
1173 @_open_only |
1119 def rtype_eids_rdef(self, rtype, eidfrom, eidto): |
1174 def rtype_eids_rdef(self, rtype, eidfrom, eidto): |
1120 # use type_and_source_from_eid instead of type_from_eid for optimization |
1175 # use type_and_source_from_eid instead of type_from_eid for optimization |
1121 # (avoid two extra methods call) |
1176 # (avoid two extra methods call) |
1122 subjtype = self.repo.type_and_source_from_eid(eidfrom, self)[0] |
1177 subjtype = self.repo.type_and_source_from_eid(eidfrom, self)[0] |
1123 objtype = self.repo.type_and_source_from_eid(eidto, self)[0] |
1178 objtype = self.repo.type_and_source_from_eid(eidto, self)[0] |