|
| 1 | +import pyaxl |
| 2 | +import logging |
| 3 | +from copy import copy |
| 4 | +from suds.sax.text import Text |
| 5 | +from suds.sudsobject import Object |
| 6 | + |
| 7 | +from pyaxl import exceptions |
| 8 | +from pyaxl.axlhandler import AXLClient |
| 9 | + |
| 10 | + |
| 11 | +PF_LIST = 'list' |
| 12 | +PF_GET = 'get' |
| 13 | +PF_UPDATE = 'update' |
| 14 | +PF_ADD = 'add' |
| 15 | +PF_REMOVE = 'remove' |
| 16 | +XSD_NS = 'ns0' |
| 17 | +ESCAPES = dict(cls='class') |
| 18 | + |
| 19 | +log = logging.getLogger('pyaxl') |
| 20 | + |
| 21 | + |
| 22 | +class BaseCCModel(Object): |
| 23 | + """ Provide base functionality for Abstract |
| 24 | + or XTypes Objects. |
| 25 | + """ |
| 26 | + |
| 27 | + __name__ = '' |
| 28 | + __configname__ = '' |
| 29 | + __config__ = None |
| 30 | + __client__ = None |
| 31 | + __attached__ = False |
| 32 | + __updateable__ = list() |
| 33 | + |
| 34 | + def __init__(self, *args, **kwargs): |
| 35 | + """ if no arguments are given this object will be created as |
| 36 | + empty object. Else it will find and fetch the data from |
| 37 | + callmanager and fill this object up. |
| 38 | + """ |
| 39 | + configname = 'default' |
| 40 | + if 'configname' in kwargs: |
| 41 | + configname = kwargs['configname'] |
| 42 | + del kwargs['configname'] |
| 43 | + self._configure(configname) |
| 44 | + self._initalize(args, kwargs) |
| 45 | + |
| 46 | + def __setattr__(self, name, value): |
| 47 | + """ remember which attributes was changed. Generally used for |
| 48 | + the update method. |
| 49 | + """ |
| 50 | + if hasattr(self, '__keylist__') and name in self.__keylist__: |
| 51 | + self.__updateable__.append(name) |
| 52 | + super(BaseCCModel, self).__setattr__(name, value) |
| 53 | + |
| 54 | + @classmethod |
| 55 | + def _axl_method(cls, prefix, name, client): |
| 56 | + """ return a function to call the callmanager. |
| 57 | + """ |
| 58 | + return getattr(client.service, '%s%s' % (prefix, name,)) |
| 59 | + |
| 60 | + @classmethod |
| 61 | + def _prepare_result(cls, result, returns): |
| 62 | + """ unwrap suds object as tuple and return a generator. |
| 63 | + """ |
| 64 | + unwrapped = result['return'] |
| 65 | + if isinstance(unwrapped, str): |
| 66 | + return |
| 67 | + unwrapped = unwrapped[0] |
| 68 | + for obj in unwrapped: |
| 69 | + yield tuple([getattr(obj, r) for r in returns]) |
| 70 | + |
| 71 | + def _initalize(self, args, kwargs): |
| 72 | + """ a part of init method. If some search criteria was found it |
| 73 | + will automatically load this object. |
| 74 | + """ |
| 75 | + if not args and not kwargs: |
| 76 | + self._create_empty() |
| 77 | + return |
| 78 | + self._load(args, kwargs) |
| 79 | + |
| 80 | + def _load(self, args, kwargs): |
| 81 | + """ call the callmanager and load the required object. |
| 82 | + """ |
| 83 | + first_lower = lambda s: s[:1].lower() + s[1:] if s else '' |
| 84 | + method = self._axl_method(PF_GET, self.__name__, self.__client__) |
| 85 | + result = method(*args, **kwargs) |
| 86 | + result = getattr(getattr(result, 'return'), first_lower(self.__name__)) |
| 87 | + self._loadattr(result) |
| 88 | + self.__attached__ = True |
| 89 | + |
| 90 | + def _convert_ecaped(self, kw): |
| 91 | + """ convert tags like "cls" to "class". This is normally |
| 92 | + done by suds, but by creating or update an object |
| 93 | + the attribute are not converted. This function will fix it. |
| 94 | + """ |
| 95 | + for key, value in kw.items(): |
| 96 | + if key in ESCAPES: |
| 97 | + del kw[key] |
| 98 | + kw[ESCAPES[key]] = value |
| 99 | + return kw |
| 100 | + |
| 101 | + def _skip_empty_tags(self, obj): |
| 102 | + """ callmanager can't handle attributes that are empty. |
| 103 | + This will recursive create a copy of object and remove |
| 104 | + all empty tags. |
| 105 | + """ |
| 106 | + copyobj = copy(obj) |
| 107 | + keylist = list() |
| 108 | + for key in obj.__keylist__: |
| 109 | + value = getattr(obj, key) |
| 110 | + if isinstance(value, list): |
| 111 | + copyobj[key] = [i if isinstance(i, Text) else self._skip_empty_tags(i) for i in value] |
| 112 | + keylist.append(key) |
| 113 | + elif isinstance(value, Object): |
| 114 | + copyobj[key] = self._skip_empty_tags(value) |
| 115 | + keylist.append(key) |
| 116 | + else: |
| 117 | + if isinstance(value, Text) and value != '' and value is not None: |
| 118 | + keylist.append(key) |
| 119 | + else: |
| 120 | + del copyobj.__dict__[key] |
| 121 | + copyobj.__keylist__ = keylist |
| 122 | + return copyobj |
| 123 | + |
| 124 | + def _configure(self, configname): |
| 125 | + """ a part of init method. If no name is given it will |
| 126 | + take automatically the name of the class. |
| 127 | + """ |
| 128 | + self.__client__ = AXLClient.get_client(configname) |
| 129 | + self.__config__ = pyaxl.configuration.registry.get(configname) |
| 130 | + self.__configname__ = configname |
| 131 | + if self.__name__ is '': |
| 132 | + self.__name__ = self.__class__.__name__ |
| 133 | + |
| 134 | + def _create_empty(self): |
| 135 | + """ create an empty object. All attributes are set |
| 136 | + from a xsd type. |
| 137 | + """ |
| 138 | + obj = self.__client__.factory.create('%s:X%s' % (XSD_NS, self.__name__,)) |
| 139 | + self._loadattr(obj) |
| 140 | + |
| 141 | + def _loadattr(self, sudsinst): |
| 142 | + """ merge a suds object in this object... yes, python |
| 143 | + is so powerful :-O |
| 144 | +
|
| 145 | + first: update object attributes with suds attributes. |
| 146 | + second: copy all attributes of class instance to object. |
| 147 | +
|
| 148 | + The result will be a object that has all attributes as theses in XDS. |
| 149 | + """ |
| 150 | + |
| 151 | + self.__dict__.update(sudsinst.__dict__) |
| 152 | + for k in sudsinst.__dict__.keys(): |
| 153 | + if hasattr(self.__class__, k): |
| 154 | + self.__dict__[k] = self.__class__.__dict__[k] |
| 155 | + |
| 156 | + |
| 157 | +class AbstractCCMModel(BaseCCModel): |
| 158 | + """ Base class for all CiscoCallmanager objects. |
| 159 | + This will make the bridge between SUDS and CCM |
| 160 | + objects. In addition all standard method are implement here. |
| 161 | + """ |
| 162 | + |
| 163 | + def create(self): |
| 164 | + """ add this object to callmanager. |
| 165 | + """ |
| 166 | + if self.__attached__: |
| 167 | + raise exceptions.CreationException('this object are already attached') |
| 168 | + method = self._axl_method(PF_ADD, self.__name__, self.__client__) |
| 169 | + xtype = self.__client__.factory.create('%s:%s' % (XSD_NS, method.method.name)) |
| 170 | + xtype = xtype[1] # take attributes from wrapper |
| 171 | + tags = xtype.__keylist__ + list(ESCAPES.keys()) |
| 172 | + unwrapped = dict() |
| 173 | + for key in self.__keylist__: |
| 174 | + value = getattr(self, key) |
| 175 | + if key in tags and value != '' and value is not None: |
| 176 | + if isinstance(value, Object): |
| 177 | + unwrapped[key] = self._skip_empty_tags(value) |
| 178 | + else: |
| 179 | + unwrapped[key] = value |
| 180 | + unwrapped = self._convert_ecaped(unwrapped) |
| 181 | + result = method(unwrapped) |
| 182 | + uuid = result['return'] |
| 183 | + self.__attached__ = True |
| 184 | + self._uuid = uuid |
| 185 | + self.__updateable__ = list() |
| 186 | + log.info('new %s was created, uuid=%s' % (self.__name__, uuid,)) |
| 187 | + return uuid |
| 188 | + |
| 189 | + def update(self): |
| 190 | + """ all attributes that was changed will be committed to the callmanager. |
| 191 | + """ |
| 192 | + if not self.__attached__: |
| 193 | + raise exceptions.UpdateException('you must create a object with "create" before update') |
| 194 | + method = self._axl_method(PF_UPDATE, self.__name__, self.__client__) |
| 195 | + xtype = self.__client__.factory.create('%s:%s' % (XSD_NS, method.method.name)) |
| 196 | + tags = xtype.__keylist__ + list(ESCAPES.keys()) |
| 197 | + unwrapped = dict([(i, getattr(self, i),) for i in self.__updateable__ if i in tags]) |
| 198 | + unwrapped.update(dict(uuid=self._uuid)) |
| 199 | + unwrapped = self._convert_ecaped(unwrapped) |
| 200 | + method(**unwrapped) |
| 201 | + self.__updateable__ = list() |
| 202 | + log.info('%s was updated, uuid=%s' % (self.__name__, self._uuid,)) |
| 203 | + |
| 204 | + def remove(self): |
| 205 | + """ delete this object. |
| 206 | + """ |
| 207 | + if not self.__attached__: |
| 208 | + msg = 'This object is not attached and can not removed from callmanager' |
| 209 | + raise exceptions.RemoveException(msg) |
| 210 | + method = self._axl_method(PF_REMOVE, self.__name__, self.__client__) |
| 211 | + method(uuid=self._uuid) |
| 212 | + self._uuid = None |
| 213 | + self.__attached__ = False |
| 214 | + log.info('%s was removed, uuid=%s' % (self.__name__, self._uuid,)) |
| 215 | + |
| 216 | + def reload(self, force=False): |
| 217 | + """ Reload an object. |
| 218 | + """ |
| 219 | + if not self.__attached__: |
| 220 | + msg = 'This object is not attached and can not reloaded from callmanager' |
| 221 | + raise exceptions.ReloadException(msg) |
| 222 | + if not force and len(self.__updateable__): |
| 223 | + msg = 'Error because some field are already changed by the client. Use force or update it first.' |
| 224 | + raise exceptions.ReloadException(msg) |
| 225 | + self._load(list(), dict(uuid=self._uuid)) |
| 226 | + |
| 227 | + def clone(self): |
| 228 | + """ Clone a existing object. After cloning the new object will |
| 229 | + be detached. This means it can directly added to the callmanager |
| 230 | + with the create method. |
| 231 | + """ |
| 232 | + obj = self.__class__() |
| 233 | + #obj.__dict__.update(self.__dict__) |
| 234 | + for i in ['__updateable__', '__keylist__', ] + self.__keylist__: |
| 235 | + obj.__dict__[i] = copy(getattr(self, i)) |
| 236 | + obj._uuid = None |
| 237 | + obj.__attached__ = False |
| 238 | + log.debug('%s was cloned' % self.__name__) |
| 239 | + return obj |
| 240 | + |
| 241 | + @classmethod |
| 242 | + def list(cls, criteria, returns, skip=None, first=None, configname='default'): |
| 243 | + """ find all object with the given search criteria. It also |
| 244 | + required a list with return values. The return value is a |
| 245 | + generator and the next call will return a tuple with the returnsValues. |
| 246 | + """ |
| 247 | + client = AXLClient.get_client(configname) |
| 248 | + method = cls._axl_method(PF_LIST, cls.__name__, client) |
| 249 | + tags = dict([(i, True) for i in returns]) |
| 250 | + log.debug('fetch list of %ss, search criteria=%s' % (cls.__name__, str(criteria))) |
| 251 | + args = criteria, tags |
| 252 | + if skip is not None or first is not None: |
| 253 | + if skip is None: |
| 254 | + skip = 0 |
| 255 | + if first is None: |
| 256 | + args = criteria, tags, skip |
| 257 | + else: |
| 258 | + args = criteria, tags, skip, first |
| 259 | + return cls._prepare_result(method(*args), returns) |
| 260 | + |
| 261 | + @classmethod |
| 262 | + def list_obj(cls, criteria, skip=None, first=None, configname='default'): |
| 263 | + """ find all object with the given search criteria. |
| 264 | + The return value is generator. Each next call will |
| 265 | + fetch a new instance and return it as object. |
| 266 | + """ |
| 267 | + for uuid, in cls.list(criteria, ('_uuid',), skip, first, configname): |
| 268 | + yield cls(uuid=uuid) |
| 269 | + |
| 270 | + |
| 271 | +class AbstractXType(BaseCCModel): |
| 272 | + |
| 273 | + def _initalize(self, args, kwargs): |
| 274 | + """ Xtype is part of soap structure. XType will never be load directly so it's |
| 275 | + need to be created as empty object. |
| 276 | + """ |
| 277 | + self._create_empty() |
| 278 | + |
| 279 | + def _create_empty(self): |
| 280 | + """ create an empty object. All attributes are set |
| 281 | + from a xsd type. |
| 282 | + """ |
| 283 | + obj = self.__client__.factory.create('%s:%s' % (XSD_NS, self.__name__,)) |
| 284 | + self._loadattr(obj) |
| 285 | + |
| 286 | + |
| 287 | +class AbstractXTypeListItem(dict): |
| 288 | + """ A special XType that can be used to fill into a list. |
| 289 | + """ |
| 290 | + def __init__(self, *args, **kwargs): |
| 291 | + name = self.__class__.__name__ |
| 292 | + xtype = type(name, (AbstractXType, self.__class__), dict())(*args, **kwargs) |
| 293 | + self[name[1:]] = xtype |
0 commit comments