Package Products :: Package ZenRelations :: Module ZenPropertyManager |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenRelations.ZenPropertyManager

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2007, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  import re
     15
     16  from OFS.PropertyManager import PropertyManager
     17  from zExceptions import BadRequest
     18  from Globals import DTMLFile
     19  from Globals import InitializeClass
     20  from Acquisition import aq_base, aq_chain
     21  from ZPublisher.Converters import type_converters
     22  from Products.ZenModel.ZenossSecurity import *
     23  from AccessControl import ClassSecurityInfo
     24  from Exceptions import zenmarker
     25  iszprop = re.compile("^z[A-Z]").search
     26
     27  from Products.ZenUtils.Utils import unused
     28
     29  # Z_PROPERTIES is a list of (id, type, value) pairs that define all the
     30  # zProperties.  The values are set on dmd.Devices in the
     31  # buildDeviceTreeProperties of DeviceClass
     32  Z_PROPERTIES = [
     33
     34      # zPythonClass maps device class to python classs (separate from device
     35      # class name)
     36      ('zPythonClass', '', 'string'),
     37
     38      # zProdStateThreshold is the production state threshold at which to start
     39      # monitoring boxes
     40      ('zProdStateThreshold', 300, 'int'),
     41
     42      # zIfDescription determines whether or not the ifdescripion field is
     43      # displayed
     44      ('zIfDescription', False, 'boolean'),
     45
     46      # Snmp collection properties
     47      ('zSnmpCommunities', ['public', 'private'], 'lines'),
     48      ('zSnmpCommunity', 'public', 'string'),
     49      ('zSnmpPort', 161, 'int'),
     50      ('zSnmpVer', 'v1', 'string'),
     51      ('zSnmpTries', 2, 'int'),
     52      ('zSnmpTimeout', 2.5, 'float'),
     53      ('zSnmpSecurityName', '', 'string'),
     54      ('zSnmpAuthPassword', '', 'password'),
     55      ('zSnmpPrivPassword', '', 'password'),
     56      ('zSnmpAuthType', '', 'string'),
     57      ('zSnmpPrivType', '', 'string'),
     58      ('zRouteMapCollectOnlyLocal', False, 'boolean'),
     59      ('zRouteMapCollectOnlyIndirect', False, 'boolean'),
     60      ('zRouteMapMaxRoutes', 500, 'int'),
     61      ('zInterfaceMapIgnoreTypes', '', 'string'),
     62      ('zInterfaceMapIgnoreNames', '', 'string'),
     63      ('zFileSystemMapIgnoreTypes', [], 'lines'),
     64      ('zFileSystemMapIgnoreNames', '', 'string'),
     65      ('zFileSystemSizeOffset', 1.0, 'float'),
     66      ('zHardDiskMapMatch', '', 'string'),
     67      ('zSysedgeDiskMapIgnoreNames', '', 'string'),
     68      ('zIpServiceMapMaxPort', 1024, 'int'),
     69      ('zDeviceTemplates', ['Device'], 'lines'),
     70      ('zLocalIpAddresses', '^127|^0\\.0|^169\\.254|^224', 'string'),
     71      ('zLocalInterfaceNames', '^lo|^vmnet', 'string'),
     72
     73      # Status monitor properties
     74      ('zSnmpMonitorIgnore', False, 'boolean'),
     75      ('zPingMonitorIgnore', False, 'boolean'),
     76      ('zWmiMonitorIgnore', True, 'boolean'),
     77      ('zStatusConnectTimeout', 15.0, 'float'),
     78
     79      # DataCollector properties
     80      ('zCollectorPlugins', [], 'lines'),
     81      ('zCollectorClientTimeout', 180, 'int'),
     82      ('zCollectorDecoding', 'latin-1', 'string'),
     83      ('zCommandUsername', '', 'string'),
     84      ('zCommandPassword', '', 'password'),
     85      ('zCommandProtocol', 'ssh', 'string'),
     86      ('zCommandPort', 22, 'int'),
     87      ('zCommandLoginTries', 1, 'int'),
     88      ('zCommandLoginTimeout', 10.0, 'float'),
     89      ('zCommandCommandTimeout', 10.0, 'float'),
     90      ('zCommandSearchPath', [], 'lines'),
     91      ('zCommandExistanceTest', 'test -f %s', 'string'),
     92      ('zCommandPath', '/usr/local/zenoss/libexec', 'string'),
     93      ('zTelnetLoginRegex', 'ogin:.$', 'string'),
     94      ('zTelnetPasswordRegex', 'assword:', 'string'),
     95      ('zTelnetSuccessRegexList', ['\\$.$', '\\#.$'], 'lines'),
     96      ('zTelnetEnable', False, 'boolean'),
     97      ('zTelnetEnableRegex', 'assword:', 'string'),
     98      ('zTelnetTermLength', True, 'boolean'),
     99      ('zTelnetPromptTimeout', 10.0, 'float'),
    100      ('zKeyPath', '~/.ssh/id_dsa', 'string'),
    101      ('zMaxOIDPerRequest', 40, 'int'),
    102
    103      # Extra stuff for users
    104      ('zLinks', '', 'string'),
    105
    106      # Windows WMI collector properties
    107      ('zWinUser', '', 'string'),
    108      ('zWinPassword', '', 'password'),
    109      ('zWinEventlogMinSeverity', 2, 'int'),
    110      ('zWinEventlog', False, 'boolean'),
    111
    112      # zIcon is the icon path
    113      ('zIcon', '/zport/dmd/img/icons/noicon.png', 'string'),
    114      ]
    115
    116 -class PropertyDescriptor(object):
    117      """
    118      Transforms the property value based on its type.
    119
    120      Follows the Descriptor protocol defined at
    121      http://docs.python.org/reference/datamodel.html#descriptors
    122      """
    123
    124 -    def __init__(self, id, type, transformer):
    125          self.id = id
    126          self.type = type
    127          self.transformer = transformer
    128
    129 -    def __get__(self, instance, owner):
    130          """
    131          Returns self for class attribute access.  Returns the transformed
    132          value for instance attribute access.
    133          """
    134          try:
    135              if instance is None:
    136                  retval = self
    137              else:
    138                  self._migrate(instance)
    139                  value = instance._propertyValues[self.id]
    140                  retval = self._transform(instance, value, 'transformForGet')
    141              return retval
    142          except:
    143              raise AttributeError
    144
    145 -    def __set__(self, instance, value):
    146          """
    147          Transforms the value and sets it.
    148          """
    149          self._migrate(instance)
    150          self._set(instance, value)
    151
    152 -    def __delete__(self, instance):
    153          """
    154          Delete the property.
    155          """
    156          self._migrate(instance)
    157          del instance._propertyValues[self.id]
    158
    159 -    def _migrate(self, instance):
    160          """
    161          If the id is in __dict__ then move the value to the _propertyValues
    162          dictionary. Check to make sure that the type of this descriptor class
    163          and the type in the Zope OFS PropertyManager metadata are the same.
    164          """
    165          if not hasattr(instance, '_propertyValues'):
    166              instance._propertyValues = {}
    167          if self.id in vars(instance):
    168              self._set(instance, vars(instance)[self.id])
    169              del instance.__dict__[self.id]
    170              instance._p_changed = True
    171          for dct in instance._properties:
    172              if dct['id'] == self.id:
    173                  if dct['type'] != self.type:
    174                      dct['type'] = self.type
    175                      instance._p_changed = True
    176                  break
    177
    178 -    def _set(self, instance, value):
    179          """
    180          Transform and set the value in the _propertyValues dictionary.
    181          """
    182          valueToSet = self._transform(instance, value, 'transformForSet')
    183          instance._propertyValues[self.id] = valueToSet
    184
    185 -    def _transform(self, instance, value, method):
    186          """
    187          Lookup the transformer for the type and transform the value. The
    188          method parameter can be 'transformForGet' or 'transformForSet' and
    189          determines the transformer method that is called.
    190          """
    191          return getattr(self.transformer, method)(value)
    192
    193 -class ZenPropertyManager(object, PropertyManager):
    194      """
    195      ZenPropertyManager adds keyedselection type to PropertyManager.
    196      A keyedselection displayes a different name in the popup then
    197      the actual value the popup will have.
    198
    199      It also has management for zenProperties which are properties that can be
    200      inherited long the acquision chain.  All properties are for a branch are
    201      defined on a "root node" specified by the function which must be returned
    202      by the function getZenRootNode that should be over ridden in a sub class.
    203      Prperties can then be added further "down" the aq_chain by calling
    204      setZenProperty on any contained node.
    205
    206      ZenProperties all have the same prefix which is defined by iszprop
    207      this can be overridden in a subclass.
    208
    209      ZenPropertyManager overrides getProperty and getPropertyType from
    210      PropertyManager to support acquisition. If you want to query an object
    211      about a property, but do not want it to search the acquistion chain then
    212      use the super classes method or aq_base.  Example:
    213
    214          # acquires property from dmd.Devices
    215          dmd.Devices.Server.getProperty('zCollectorPlugins')
    216
    217          # does not acquire property from dmd.Devices
    218          PropertyManager.getProperty(dmd.Devices.Server, 'zCollectorPlugins')
    219
    220          # also does not acquire property from dmd.Devices
    221          aq_base(dmd.Devices.Server).getProperty('zSnmpCommunity')
    222
    223      The properties are stored as attributes which is convenient, but can be
    224      confusing.  Attribute access always uses acquistion.  Setting an
    225      attribute, will not add it to the list of properties, so subsquent calls
    226      to hasProperty or getProperty won't return it.
    227
    228      Property Transformers are stored at dmd.propertyTransformers and transform
    229      the property based on type during calls to the _setProperty,
    230      _updateProperty, and getProperty methods. Adding a property using
    231      _setProperty applies the appropriate transformer and adds its value as an
    232      attribute, but when you access it as an attribute the property transformer
    233      is again applied, but this time using its transformForGet method.
    234      """
    235      __pychecker__='no-override'
    236
    237      security = ClassSecurityInfo()
    238
    239      manage_propertiesForm=DTMLFile('dtml/properties', globals(),
    240                                     property_extensible_schema__=1)
    241
    242 -    def _setPropValue(self, id, value):
    243          """override from PerpertyManager to handle checks and ip creation"""
    244          self._wrapperCheck(value)
    245          propType = self.getPropertyType(id)
    246          if  propType == 'keyedselection':
    247              value = int(value)
    248          if not getattr(self,'_v_propdict',False):
    249              self._v_propdict = self.propdict()
    250          if self._v_propdict.has_key('setter'):
    251              settername = self._v_propdict['setter']
    252              setter = getattr(aq_base(self), settername, None)
    253              if not setter:
    254                  raise ValueError("setter %s for property %s doesn't exist"
    255                                      % (settername, id))
    256              if not callable(setter):
    257                  raise TypeError("setter %s for property %s not callable"
    258                                      % (settername, id))
    259              setter(value)
    260          else:
    261              setattr(self, id, value)
    262
    263
    264 -    def _setProperty(self, id, value, type='string', label=None,
    265                      visible=True, setter=None):
    266          """for selection and multiple selection properties
    267          the value argument indicates the select variable
    268          of the property
    269          """
    270          self._wrapperCheck(value)
    271          if not self.valid_property_id(id):
    272              raise BadRequest, 'Id %s is invalid or duplicate' % id
    273
    274          def setprops(**pschema):
    275              self._properties=self._properties+(pschema,)
    276              if setter: pschema['setter'] = setter
    277              if label: pschema['label'] = label
    278
    279          if type in ('selection', 'multiple selection'):
    280              if not hasattr(self, value):
    281                  raise BadRequest, 'No select variable %s' % value
    282              setprops(id=id,type=type, visible=visible,
    283                       select_variable=value)
    284              if type=='selection':
    285                  self._setPropValue(id, '')
    286              else:
    287                  self._setPropValue(id, [])
    288          else:
    289              setprops(id=id, type=type, visible=visible)
    290              self._setPropValue(id, value)
    291
    292 -    def _updateProperty(self, id, value):
    293          """ This method sets a property on a zope object. It overrides the
    294          method in PropertyManager. If Zope is upgraded you will need to check
    295          that this method has not changed! It is overridden so that we can catch
    296          the ValueError returned from the field2* converters in the class
    297          Converters.py
    298          """
    299          from Products.ZenWidgets import messaging
    300          try:
    301              super(ZenPropertyManager, self)._updateProperty(id, value)
    302          except ValueError:
    303              proptype = self.getPropertyType(id)
    304              messaging.IMessageSender(self).sendToBrowser(
    305                  'Error Saving Property %s' % id,
    306                  ("New value '%s' is of invalid type. "
    307                  "It should be type '%s'") % (value, proptype),
    308                  priority=messaging.CRITICAL
    309                  )
    310
    311
    312      _onlystars = re.compile("^\*+$").search
    313      security.declareProtected(ZEN_ZPROPERTIES_EDIT, 'manage_editProperties')
    314 -    def manage_editProperties(self, REQUEST):
    315          """
    316          Edit object properties via the web.
    317          The purpose of this method is to change all property values,
    318          even those not listed in REQUEST; otherwise checkboxes that
    319          get turned off will be ignored.  Use manage_changeProperties()
    320          instead for most situations.
    321          """
    322          for prop in self._propertyMap():
    323              name=prop['id']
    324              if 'w' in prop.get('mode', 'wd'):
    325                  value=REQUEST.get(name, '')
    326                  if self.zenPropIsPassword(name) and self._onlystars(value):
    327                      continue
    328                  self._updateProperty(name, value)
    329          if getattr(self, "index_object", False):
    330              self.index_object()
    331          if REQUEST:
    332              message="Saved changes."
    333              return self.manage_propertiesForm(self,REQUEST,
    334                                                manage_tabs_message=message)
    335
    336
    337 -    def getZenRootNode(self):
    338          """sub class must implement to use zenProperties."""
    339          raise NotImplementedError
    340
    341      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyIds')
    342 -    def zenPropertyIds(self, all=True, pfilt=iszprop):
    343          """
    344          Return list of device tree property names.
    345          If all use list from property root node.
    346          """
    347          if all:
    348              rootnode = self.getZenRootNode()
    349          else:
    350              if self.id == self.dmdRootName: return []
    351              rootnode = aq_base(self)
    352          props = []
    353          for prop in rootnode.propertyIds():
    354              if not pfilt(prop): continue
    355              props.append(prop)
    356          props.sort()
    357          return props
    358
    359      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyItems')
    360 -    def zenPropertyItems(self):
    361          """Return list of (id, value) tuples of zenProperties.
    362          """
    363          return map(lambda x: (x, getattr(self, x)), self.zenPropertyIds())
    364
    365      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyMap')
    366 -    def zenPropertyMap(self, pfilt=iszprop):
    367          """Return property mapping of device tree properties."""
    368          rootnode = self.getZenRootNode()
    369          pmap = []
    370          for pdict in rootnode.propertyMap():
    371              if pfilt(pdict['id']): pmap.append(pdict)
    372          pmap.sort(lambda x, y: cmp(x['id'], y['id']))
    373          return pmap
    374
    375      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyString')
    376 -    def zenPropertyString(self, id):
    377          """Return the value of a device tree property as a string"""
    378          def displayLines(lines):
    379              return '\n'.join([str(line) for line in lines])
    380          def displayPassword(password):
    381              return '*' * len(password)
    382          def displayOthers(other):
    383              return other
    384          displayFunctions = {'lines': displayLines,
    385                              'password': displayPassword}
    386          display = displayFunctions.get(self.getPropertyType(id),
    387                                         displayOthers)
    388          return display(self.getProperty(id, ''))
    389
    390      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropIsPassword')
    391 -    def zenPropIsPassword(self, id):
    392          """Is this field a password field.
    393          """
    394          return self.getPropertyType(id) == 'password'
    395
    396      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyPath')
    397 -    def zenPropertyPath(self, id):
    398          """Return the primaryId of where a device tree property is found."""
    399          ob = self._findParentWithProperty(id)
    400          if ob is None:
    401              path = None
    402          else:
    403              path = ob.getPrimaryId(self.getZenRootNode().getId())
    404          return path
    405
    406      security.declareProtected(ZEN_ZPROPERTIES_EDIT, 'setZenProperty')
    407 -    def setZenProperty(self, propname, propvalue, REQUEST=None):
    408          """
    409          Add or set the propvalue of the property propname on this node of
    410          the device Class tree.
    411          """
    412          ptype = self.getPropertyType(propname)
    413          if ptype == 'lines':
    414              dedupedList = []
    415              for x in propvalue:
    416                  if x not in dedupedList:
    417                      dedupedList.append(x)
    418              propvalue = dedupedList
    419          if getattr(aq_base(self), propname, zenmarker) != zenmarker:
    420              self._updateProperty(propname, propvalue)
    421          else:
    422              if ptype in ("selection", 'multiple selection'): ptype="string"
    423              if type_converters.has_key(ptype):
    424                  propvalue=type_converters[ptype](propvalue)
    425              if getattr(self, propname, None) != propvalue:
    426                  self._setProperty(propname, propvalue, type=ptype)
    427          if REQUEST: return self.callZenScreen(REQUEST)
    428
    429      security.declareProtected(ZEN_ZPROPERTIES_EDIT, 'saveZenProperties')
    430 -    def saveZenProperties(self, pfilt=iszprop, REQUEST=None):
    431          """Save all ZenProperties found in the REQUEST.form object.
    432          """
    433          for name, value in REQUEST.form.items():
    434              if pfilt(name):
    435                  if self.zenPropIsPassword(name) and self._onlystars(value):
    436                      continue
    437                  if name == 'zCollectorPlugins':
    438                      if tuple(getattr(self, name, ())) != tuple(value):
    439                          self.setZenProperty(name, value)
    440                  else:
    441                      self.setZenProperty(name, value)
    442
    443          return self.callZenScreen(REQUEST)
    444
    445      security.declareProtected(ZEN_ZPROPERTIES_EDIT, 'deleteZenProperty')
    446 -    def deleteZenProperty(self, propname=None, REQUEST=None):
    447          """
    448          Delete device tree properties from the this DeviceClass object.
    449          """
    450          if propname:
    451              try:
    452                  self._delProperty(propname)
    453              except AttributeError:
    454                  #occasional object corruption where the propName is in
    455                  #_properties but not set as an attribute. filter out the prop
    456                  #and create a new _properties tuple
    457                  newProps = [x for x in self._properties if x['id'] != propname]
    458                  self._properties=tuple(newProps)
    459          if REQUEST: return self.callZenScreen(REQUEST)
    460
    461      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'zenPropertyOptions')
    462 -    def zenPropertyOptions(self, propname):
    463          "Provide a set of default options for a ZProperty"
    464          unused(propname)
    465          return []
    466
    467      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'isLocal')
    468 -    def isLocal(self, propname):
    469          """Check to see if a name is local to our current context.
    470          """
    471          v = getattr(aq_base(self), propname, zenmarker)
    472          return v != zenmarker
    473
    474      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'getOverriddenObjects')
    475 -    def getOverriddenObjects(self, propname, showDevices=False):
    476          """ Get the objects that override a property somewhere below in the tree
    477          """
    478          if showDevices:
    479              objects = []
    480              for inst in self.getSubInstances('devices'):
    481                  if inst.isLocal(propname) and inst not in objects:
    482                      objects.append(inst)
    483              for suborg in self.children():
    484                  if suborg.isLocal(propname):
    485                      objects.append(suborg)
    486                  for inst in suborg.getOverriddenObjects(propname, showDevices):
    487                      if inst not in objects:
    488                          objects.append(inst)
    489              return objects
    490
    491          return [ org for org in self.getSubOrganizers()
    492              if org.isLocal(propname) ]
    493
    494 -    def _findParentWithProperty(self, id):
    495          """
    496          Returns self or the first acquisition parent that has a property with
    497          the id.  Returns None if no parent had the id.
    498          """
    499          for ob in aq_chain(self):
    500              if isinstance(ob, ZenPropertyManager) and ob.hasProperty(id):
    501                  parentWithProperty = ob
    502                  break
    503          else:
    504              parentWithProperty = None
    505          return parentWithProperty
    506
    507 -    def hasProperty(self, id, useAcquisition=False):
    508          """
    509          Override method in PropertyManager to support acquisition.
    510          """
    511          if useAcquisition:
    512              hasProp = self._findParentWithProperty(id) is not None
    513          else:
    514              hasProp = PropertyManager.hasProperty(self, id)
    515          return hasProp
    516
    517 -    def getProperty(self, id, d=None):
    518          """
    519          Get property value and apply transformer.  Overrides method in Zope's
    520          PropertyManager class.  Acquire values from aquisiton parents if
    521          needed.
    522          """
    523          ob = self._findParentWithProperty(id)
    524          if ob is None:
    525              value = d
    526          else:
    527              value = PropertyManager.getProperty(ob, id, d)
    528          return value
    529
    530      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'getPropertyType')
    531 -    def getPropertyType(self, id):
    532          """
    533          Overrides methods from PropertyManager to support acquistion.
    534          """
    535          ob = self._findParentWithProperty(id)
    536          if ob is None:
    537              type = None
    538          else:
    539              type = PropertyManager.getPropertyType(ob, id)
    540          return type
    541
    542      security.declareProtected(ZEN_ZPROPERTIES_VIEW, 'getZ')
    543 -    def getZ(self, id):
    544          """
    545          Return the value of a zProperty on this object.  This method is used to
    546          lookup zProperties for a user with a role that doesn't have direct
    547          access to an attribute further up the acquisition path.  If the
    548          requested property is a password, then None is returned.
    549
    550          @param id: id of zProperty
    551          @type id: string
    552          @return: Value of zProperty
    553          @permission: ZEN_ZPROPERTIES_VIEW
    554
    555          >>> dmd.Devices.getZ('zSnmpPort')
    556          161
    557          >>> dmd.Devices.getZ('zWinPassword')
    558          >>>
    559          """
    560          if self.hasProperty(id, useAcquisition=True) \
    561                  and not self.zenPropIsPassword(id):
    562              returnValue = self.getProperty(id)
    563          else:
    564              returnValue = None
    565          return returnValue
    566
    567  InitializeClass(ZenPropertyManager)
    568
    569 -class IdentityTransformer(object):
    570      "A do-nothing transformer to use as the default"
    571
    572 -    def transformForGet(self, value):
    573          return value
    574
    575 -    def transformForSet(self, value):
    576          return value
    577
    578 -def monkeypatchDescriptors(zprops, transformerFactories):
    579      """
    580      monkeypatch ZenPropertyManager adding an instance of the descriptor class
    581      for each of the zProperties
    582      """
    583      for id, value, type in zprops:
    584          factory = transformerFactories.get(type, IdentityTransformer)
    585          descriptor = PropertyDescriptor(id, type, factory())
    586          setattr(ZenPropertyManager, id, descriptor)
    587
    588 -def setDescriptors(transformerFactories):
    589      """
    590      Set the property descriptors on the ZenPropertyManager class.  The
    591      transformerFactories parameter is a dictionary that maps a property type
    592      to a callable factory that produces instances with transformForGet and
    593      transformForSet methods.
    594      """
    595      # copy the core zProps
    596      zprops = Z_PROPERTIES[:]
    597
    598      # add zProps from zenpacks
    599      from Products.ZenUtils.PkgResources import pkg_resources
    600      for zpkg in pkg_resources.iter_entry_points('zenoss.zenpacks'):
    601          # fromlist is typically ZenPacks.zenoss
    602          fromlist = zpkg.module_name.split('.')[:-1]
    603          module = __import__(zpkg.module_name, globals(), locals(), fromlist)
    604          if hasattr(module, 'ZenPack'):
    605              zprops.extend(module.ZenPack.packZProperties)
    606
    607      monkeypatchDescriptors(zprops, transformerFactories)
    608
    609 -def updateDescriptors(type, transformer):
    610      """
    611      Update all descriptors with the specified type to use the specified
    612      transformer.
    613      """
    614      for var in vars(ZenPropertyManager):
    615          attr = getattr(ZenPropertyManager, var)
    616          if isinstance(attr, PropertyDescriptor) and attr.type == type:
    617              attr.transformer = transformer
    618