3 from logging 
import getLogger; log = 
getLogger(
"DQDefects.virtual_defect_mixin")
 
    4 from DQUtils 
import fetch_iovs
 
    6 from .exceptions 
import DefectUnknownError, DefectExistsError
 
    7 from .ids 
import choose_new_defect_id
 
    8 from .virtual_logic 
import DefectLogic
 
   10 from typing 
import Optional, Iterable, Mapping, Set, Sequence, List
 
   13 NONHEAD_MODIFICATION_MSG = (
"Operations which modify virtual defects can only " 
   14     "be done on the HEAD tag.")
 
   18     Navigate the node to determine dependencies. Circular references raise an  
   21     Shamelessly stolen algorithm from: 
   22     http://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/ 
   24     if resolved 
is None: resolved = []
 
   25     if unresolved 
is None: unresolved = []
 
   26     unresolved.append(node)
 
   27     for dependency 
in node.dependencies:
 
   28         if dependency 
not in resolved:
 
   29             if dependency 
in unresolved:
 
   30                 raise Exception(
'Circular reference detected: %s -> %s' 
   31                                 % (node.name, dependency.name))
 
   35     unresolved.remove(node)
 
   41     A DefectsDB mixin for managing virtual defects  
   46         super(DefectsDBVirtualDefectsMixin, self).
__init__()
 
   49         all_defects = self.defect_names | self.virtual_defect_names
 
   50         for clause 
in clauses.split():
 
   51             if clause[0] 
in (
'!', 
'-'):
 
   53             assert clause 
in all_defects, (clause + 
" is not a known defect")
 
   58         assert self.
logics_tag == 
"HEAD", NONHEAD_MODIFICATION_MSG
 
   59         assert not self._read_only, 
"Insertion on read-only database" 
   63                                comment: Optional[str] = 
None, tag: Optional[str] = 
None) -> 
None:
 
   64         defect_id = self.defect_chan_as_id(defect_name)
 
   66         assert self.defect_is_virtual(defect_id), (
"Tried to update nonvirtual" 
   67             " defect with update_virtual_defect()")
 
   71         tag = tag 
if tag 
is not None else 'HEAD' 
   72         ucomment = comment.encode(
'utf-8') 
if comment 
is not None else None 
   74         self._defect_logic_payload[
"clauses"] = clauses.encode(
'ascii')
 
   76         store = self.defect_logic_folder.storeObject
 
   77         store(0, 2**63-1, self._defect_logic_payload, defect_id, tag.encode(
'ascii'),
 
   78               (
True if tag != 
'HEAD' else False))
 
   80         if comment 
is not None:
 
   81             self.defect_logic_folder.setChannelDescription(defect_id, ucomment)
 
   83     def rename_defect(self, defect_name: str, new_defect_name: str) -> 
None:
 
   85         Rename a defect (primary or virtual).  Will keep data and channel ID. 
   86         Will fix up all virtual defect dependencies in all tags. 
   88         assert not self._read_only, 
"Channel rename on read-only database" 
   91             oldname = self.normalize_defect_names(new_defect_name)
 
   93         except DefectUnknownError:
 
   96         defect_id = self.defect_chan_as_id(defect_name)
 
   98         anew_defect_name = new_defect_name.encode(
'ascii')
 
   99         if self.defect_is_virtual(defect_id):
 
  100             self.defect_logic_folder.setChannelName(defect_id, anew_defect_name)
 
  106             self.defects_folder.setChannelName(defect_id, anew_defect_name)
 
  112         @contextlib.contextmanager
 
  113         def logics_unlocking(tag):
 
  114             atag = tag.encode(
'ascii')
 
  115             log.info(
'Unlocking tag %s', tag)
 
  116             folder = self.defect_logic_folder
 
  117             orig_status = folder.tagLockStatus(atag)
 
  119                 folder.setTagLockStatus(atag, 0)
 
  122                 folder.setTagLockStatus(atag, orig_status)
 
  123             log.info(
'Done with tag %s', tag)
 
  125         def logic_substitute(clause, oldname, newname):
 
  128             if clause[0] 
in (
'!',):
 
  131             if clause != oldname:
 
  134                 return prefix + newname
 
  136         for tag 
in [
'HEAD'] + self.logics_tags:
 
  138             for vd, logic 
in logics.items():
 
  139                 if defect_name 
in logic.realclauses:
 
  140                     newclauses = 
' '.
join([logic_substitute(x, defect_name,
 
  142                                            for x 
in logic.clauses
 
  144                     with logics_unlocking(tag):
 
  150         Create a new virtual defect 
  152         assert self.
logics_tag == 
"HEAD", NONHEAD_MODIFICATION_MSG
 
  153         assert not self._read_only, 
"Insertion on read-only database" 
  154         from DQUtils.channel_mapping 
import get_channel_ids_names
 
  157         store = self.defect_logic_folder.storeObject
 
  158         p = self._defect_logic_payload
 
  162         p[
"clauses"] = clauses.encode(
'ascii')
 
  168         log.debug(
"Creating new defect %s: 0x%08x", defect_name, defect_id)
 
  171             oldname = self.normalize_defect_names(defect_name)
 
  173         except DefectUnknownError:
 
  176         if (defect_id 
in ids):
 
  178         self.defect_logic_folder.createChannel(defect_id, defect_name.encode(
'ascii'), comment.encode(
'utf-8'))
 
  179         store(0, 2**63-1, p, defect_id)
 
  180         self._new_virtual_defect(defect_id, defect_name)
 
  185         Returns a list of DefectLogic objects that need to be evaluated,  
  186         in the correct order for them to be consistent. 
  188         `defects` should be a list of names 
  202             dependencies = logics
 
  205         resolved.remove(MasterNode)
 
  210         Determine which primary flags are used for  
  211         a given list of input `virtual_defect_names`. 
  213         primary_defects = 
set()
 
  214         for defect_logic 
in defect_logics:
 
  215             primary_defects |= defect_logic.primary_defects
 
  217         return primary_defects
 
  222         Returns all virtual defects in the form {"defect_name" : DefectLogic()} 
  223         for the tag DefectDB was constructed on. 
  233         if tag != 
"HEAD" and not self.defect_logic_folder.existsUserTag(tag.encode(
'ascii')):
 
  237         logics = 
fetch_iovs(self.defect_logic_folder, tag=tag.encode(
'ascii'), 
 
  240         logics = {l.channel: 
DefectLogic(l) 
for l 
in logics}
 
  242         for _, defect_logic 
in logics.items():
 
  243             defect_logic._populate(logics)
 
  248                                 exclude: Sequence[str] = [
'TIGHT', 
'IDTIGHT', 
'PHYS_.*']):
 
  250         Returns primary defects that are depended on by a virtual defect 
  251         if old_primary_only == True, only return those depended on by a 
  252         virtual defect with no _ in the name 
  253         ignores virtual defects in exclude list 
  254         accepts regexes in exclude list 
  258             vdl = dict(l 
for l 
in vdl.items() 
if '_' not in l[0])
 
  261             rex = re.compile(
'(%s)' % 
'|'.
join(exclude))
 
  262             vdl = dict(l 
for l 
in vdl.items() 
if not rex.match(l[0]))
 
  267         When called, uses an assertion to check that there are no missing  
  268         defects. This is a database consistency check which should never be 
  269         violated (but was when DQDefects was new). 
  271         all_defects = self.defect_names | self.virtual_defect_names
 
  276             used_defects.update(logic.realclauses)
 
  277             for clause 
in logic.realclauses:
 
  278                 used_by.setdefault(clause, []).
append(defect)
 
  280         missing_defects = used_defects - all_defects
 
  283             log.error(
"-- The missing defects are used by the following virtual defects")
 
  284             log.error(
"<missing> : <used by>")
 
  285             for missing 
in missing_defects:
 
  286                 log.error(missing + 
":" + 
", ".
join(used_by[missing]))
 
  288         assert not missing_defects, (
"The following defects are used but not " 
  289                                      "defined anywhere: %r" % missing_defects)