File indexing completed on 2024-11-03 05:12:55
0001 # -*- coding: UTF-8 -*- 0002 0003 """ 0004 Framework for monitored classes. 0005 0006 Includes the base class that monitored classes should inherit from, 0007 and some monitored partial counterparts to standard Python data types. 0008 Monitored objects are limited to prescribed set of public instance variables, 0009 and, optionally, the values which can be assigned to those are limited to 0010 a prescribed set of types. Each public instance variable has a I{shadowing} 0011 modification counter, an instance variable which counts the changes made to 0012 the variable which it shadows. 0013 0014 As of yet, this module and its functionality is for internal use in 0015 core PO interface classes (L{Catalog}, L{Message}...), not intended 0016 for creation of monitored classes in client code. 0017 Use this documentation only to find out which of the methods available 0018 in standard data types are available through their monitored counterparts 0019 (e.g. L{Monlist} compared to C{list}). 0020 0021 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0022 @license: GPLv3 0023 """ 0024 0025 from pology import PologyError, _, n_ 0026 0027 # ============================================================================= 0028 # Internal functions. 0029 0030 def _gather_modcount (obj): 0031 modcount = 0 0032 for cnt in list(getattr(obj, "#", {}).values()): # own counts 0033 modcount += cnt 0034 for att in getattr(obj, "_spec", {}): # sub counts 0035 if att != "*": # single sub counts 0036 if not obj._spec[att].get("derived", False): 0037 modcount += getattr(obj.__dict__["_" + att], "modcount", 0) 0038 else: 0039 for itemobj in obj.__dict__[att]: # sequence sub counts 0040 modcount += getattr(itemobj, "modcount", 0) 0041 return modcount 0042 0043 def _scatter_modcount (obj, val): 0044 if hasattr(obj, "#"): 0045 for att in obj.__dict__["#"]: 0046 obj.__dict__["#"][att] = val 0047 for att in getattr(obj, "_spec", {}): 0048 if att != "*": 0049 if not obj._spec[att].get("derived", False): 0050 subobj = obj.__dict__["_" + att] 0051 if hasattr(subobj, "modcount"): 0052 subobj.modcount = val 0053 else: 0054 for itemobj in obj.__dict__[att]: 0055 if hasattr(itemobj, "modcount"): 0056 itemobj.modcount = val 0057 0058 def _assert_spec_single (att, obj, spec): 0059 if "type" in spec: 0060 if not isinstance(obj, spec["type"]): 0061 if att != "*": 0062 raise PologyError( 0063 _("@info", 0064 "Expected %(type1)s for attribute '%(attr)s', " 0065 "got %(type2)s.", 0066 type1=spec["type"], attr=att, type2=type(obj))) 0067 else: 0068 raise PologyError( 0069 _("@info", 0070 "Expected %(type1)s for sequence element, " 0071 "got %(type2)s.", 0072 type1=spec["type"], type2=type(obj))) 0073 if "spec" in spec: 0074 _assert_spec_init(obj, spec["spec"]) 0075 0076 def _assert_spec_init (self, spec): 0077 for att, subspec in list(spec.items()): 0078 if att != "*": 0079 if not subspec.get("derived", False): 0080 _assert_spec_single(att, self.__dict__["_" + att], subspec) 0081 else: 0082 for itemobj in self.__dict__[att]: 0083 _assert_spec_single(att, itemobj, subspec) 0084 # All checks done, add spec and counts. 0085 self._spec = spec 0086 self.__dict__["#"] = {} 0087 for att in spec: 0088 if not spec[att].get("derived", False): 0089 self.__dict__["#"][att] = 0 0090 0091 # ============================================================================= 0092 # Base class for monitored classes. 0093 0094 class Monitored (object): 0095 """ 0096 Base class for monitored classes. 0097 0098 Internal. 0099 """ 0100 0101 def __getattr__ (self, att): 0102 try: 0103 attp = "_" + att 0104 if att.startswith("_"): 0105 return self.__dict__[att] 0106 elif att == "modcount": 0107 return _gather_modcount(self) 0108 elif att.endswith("_modcount"): 0109 return self.__dict__["#"][att[:-len("_modcount")]] 0110 elif att == "#": 0111 return self.__dict__[att] 0112 else: 0113 return self.__dict__[attp] 0114 except KeyError: 0115 raise AttributeError 0116 0117 def __setattr__ (self, att, val): 0118 if att.startswith("_"): 0119 self.__dict__[att] = val 0120 else: 0121 if att == "modcount" or att.endswith("_modcount"): 0122 # Only set if given to 0, ignore silently other values. 0123 if isinstance(val, int) and val == 0: 0124 if att == "modcount": 0125 _scatter_modcount(self, val) 0126 else: 0127 attb = att[:-len("_modcount")] 0128 attbp = "_" + attb 0129 self.__dict__["#"][attb] = val 0130 _scatter_modcount(self.__dict__[attbp], val) 0131 else: 0132 self.assert_spec_setattr(att, val) 0133 attp = "_" + att 0134 cval = self.__dict__[attp] 0135 if cval != val: 0136 self.__dict__["#"][att] += 1 0137 if hasattr(cval, "modcount"): 0138 mc_diff = cval.modcount - val.modcount 0139 if mc_diff > 0: 0140 self.__dict__["#"][att] += mc_diff 0141 self.__dict__[attp] = val 0142 0143 def __eq__ (self, other): 0144 return isinstance(self, type(other)) and self.data() == other.data() 0145 0146 def __ne__ (self, other): 0147 return not isinstance(self, type(other)) or self.data() != other.data() 0148 0149 def data (self): 0150 if hasattr(self, "_spec"): 0151 d = {} 0152 for att in self._spec: 0153 if att != "*": 0154 subobj = self.__getattr__(att) 0155 else: 0156 subobj = self.__dict__[att] 0157 if hasattr(subobj, "data"): 0158 d[att] = subobj.data() 0159 else: 0160 d[att] = subobj 0161 d["#"] = self.__dict__["#"] 0162 return d 0163 0164 def assert_spec_init (self, spec): 0165 _assert_spec_init(self, spec) 0166 0167 def assert_spec_setattr (self, att, subobj): 0168 if not hasattr(self, "_spec"): 0169 return 0170 if att in self._spec: 0171 spec = self._spec[att] 0172 if spec.get("derived", False): 0173 raise PologyError( 0174 _("@info", 0175 "Derived attribute '%(attr)s' is read-only.", 0176 attr=att)) 0177 _assert_spec_single(att, subobj, spec) 0178 elif att.endswith("_modcount"): 0179 if not isinstance(subobj, int): 0180 raise PologyError( 0181 _("@info", 0182 "Expected %(type1)s for attribute '%(attr)s', " 0183 "got %(type2)s.", 0184 type1=int, attr=att, type2=type(subobj))) 0185 else: 0186 raise PologyError( 0187 _("@info", 0188 "Attribute '%(attr)s' is not among specified.", 0189 attr=att)) 0190 0191 def assert_spec_getattr (self, att): 0192 if not hasattr(self, "_spec"): 0193 return 0194 if att not in self._spec: 0195 raise PologyError( 0196 _("@info", 0197 "Attribute '%(attr)s' is not among specified.", 0198 attr=att)) 0199 0200 def assert_spec_setitem (self, itemobj): 0201 if not hasattr(self, "_spec"): 0202 return 0203 if "*" in self._spec: 0204 _assert_spec_single("*", itemobj, self._spec["*"]) 0205 else: 0206 raise PologyError( 0207 _("@info", 0208 "Object '%(obj)s' is not specified to be a sequence.", 0209 obj=self)) 0210 0211 def assert_spec_getitem (self): 0212 if not hasattr(self, "_spec"): 0213 return 0214 if "*" not in self._spec: 0215 raise PologyError( 0216 _("@info", 0217 "Object '%(obj)s' is not specified to be a sequence.", 0218 obj=self)) 0219 0220 # ============================================================================= 0221 # Monitored pair. 0222 0223 _Monpair_spec = { 0224 "first" : {}, 0225 "second" : {}, 0226 } 0227 0228 class Monpair (Monitored): 0229 """ 0230 Monitored pair (counterpart to two-element C{tuple}). 0231 0232 @ivar first: the first element of the pair 0233 @ivar second: the second element of the pair 0234 """ 0235 0236 def __init__ (self, init=None): 0237 """ 0238 Create a pair with two elements. 0239 0240 All methods behave as their namesakes in standard C{tuple}. 0241 0242 @param init: 2-element sequence or another pair 0243 @param init: tuple, list,... or Monpair 0244 """ 0245 0246 if not isinstance(init, Monpair): 0247 pair = tuple(init) 0248 if len(pair) != 2: 0249 raise PologyError( 0250 _("@info", 0251 "Initializer sequence for a pair must contain " 0252 "exactly two elements.")) 0253 self._first, self._second = pair 0254 else: 0255 self._first = init.first 0256 self._second = init.second 0257 self.assert_spec_init(_Monpair_spec) 0258 0259 0260 def __repr__ (self): 0261 0262 elfmt = ", ".join((repr(self._first), repr(self._second))) 0263 return "%s([%s])" % (self.__class__.__name__, elfmt) 0264 0265 0266 def __str__ (self): 0267 0268 return self.__repr__() 0269 0270 0271 def __len__ (self): 0272 0273 return 2 0274 0275 0276 def __iter__ (self): 0277 0278 return iter((self._first, self._second)) 0279 0280 0281 def __getitem__ (self, i): 0282 0283 if i == 0: 0284 return self._first 0285 elif i == 1: 0286 return self._second 0287 else: 0288 raise IndexError 0289 0290 0291 # ============================================================================= 0292 # Monitored list. 0293 0294 _Monlist_spec = { 0295 "*" : {}, 0296 } 0297 0298 class Monlist (Monitored): 0299 """ 0300 Monitored list. 0301 """ 0302 0303 def __init__ (self, lst=None): 0304 """ 0305 Create a monitored list from a sequence. 0306 0307 All methods behave as their namesakes in standard C{list}. 0308 0309 @param lst: sequence of elements 0310 @type lst: any convertible into list by C{list()} 0311 """ 0312 0313 if lst is not None: 0314 self.__dict__["*"] = list(lst) 0315 else: 0316 self.__dict__["*"] = list() 0317 self.assert_spec_init(_Monlist_spec) 0318 0319 0320 def __repr__ (self): 0321 0322 elfmt = ", ".join(repr(x) for x in self.__dict__["*"]) 0323 return "%s([%s])" % (self.__class__.__name__, elfmt) 0324 0325 0326 def __str__ (self): 0327 0328 return self.__repr__() 0329 0330 0331 def __len__ (self): 0332 0333 return len(self.__dict__["*"]) 0334 0335 0336 def __getitem__ (self, i): 0337 0338 self.assert_spec_getitem() 0339 if not isinstance(i, slice): 0340 return self.__dict__["*"][i] 0341 else: 0342 return Monlist(self.__dict__["*"][i]) 0343 0344 0345 def __setitem__ (self, i, val): 0346 0347 if not isinstance(i, slice): 0348 self.assert_spec_setitem(val) 0349 else: 0350 for v in val: 0351 self.assert_spec_setitem(v) 0352 cval = self.__dict__["*"][i] 0353 if cval != val: 0354 self.__dict__["#"]["*"] += 1 0355 if hasattr(cval, "modcount"): 0356 mc_diff = cval.modcount - val.modcount 0357 if mc_diff > 0: 0358 self.__dict__["#"]["*"] += mc_diff 0359 self.__dict__["*"][i] = val 0360 0361 0362 def __delitem__ (self, i): 0363 0364 self.assert_spec_getitem() 0365 nitems = len(self.__dict__["*"]) 0366 del self.__dict__["*"][i] 0367 if len(self.__dict__["*"]) != nitems: 0368 self.__dict__["#"]["*"] += 1 0369 0370 0371 def __eq__ (self, other): 0372 0373 if len(self.__dict__["*"]) != len(other): 0374 return False 0375 for i in range(len(other)): 0376 if self.__dict__["*"][i] != other[i]: 0377 return False 0378 return True 0379 0380 0381 def __ne__ (self, other): 0382 0383 return not self.__eq__(other) 0384 0385 0386 def __add__ (self, other): 0387 0388 lst = Monlist(self.__dict__["*"]) 0389 lst.extend(other) 0390 return lst 0391 0392 0393 def append (self, val): 0394 0395 self.assert_spec_setitem(val) 0396 self.__dict__["*"].append(val) 0397 self.__dict__["#"]["*"] += 1 0398 0399 0400 def extend (self, other): 0401 0402 for val in other: 0403 self.append(val) 0404 0405 0406 def remove (self, val): 0407 0408 self.assert_spec_setitem(val) 0409 if val in self.__dict__["*"]: 0410 self.__dict__["*"].remove(val) 0411 self.__dict__["#"]["*"] += 1 0412 0413 0414 def pop (self, i=None): 0415 0416 if i is None: 0417 val = self.__dict__["*"].pop() 0418 else: 0419 val = self.__dict__["*"].pop(i) 0420 self.__dict__["#"]["*"] += 1 0421 return val 0422 0423 0424 def insert (self, i, val): 0425 0426 self.assert_spec_setitem(val) 0427 self.__dict__["*"].insert(i, val) 0428 self.__dict__["#"]["*"] += 1 0429 0430 0431 # ============================================================================= 0432 # Monitored set. 0433 0434 _Monset_spec = { 0435 "*" : {}, 0436 } 0437 0438 class Monset (Monitored): 0439 """ 0440 Monitored set. 0441 """ 0442 0443 def __init__ (self, st=None): 0444 """ 0445 Create a monitored set from a sequence. 0446 0447 All methods behave as their namesakes in standard C{set}. 0448 0449 @param st: sequence of elements 0450 @type st: any convertible into list by C{list()} 0451 """ 0452 0453 self.__dict__["*"] = list() 0454 if st is not None: 0455 for val in st: 0456 if val not in self.__dict__["*"]: 0457 self.__dict__["*"].append(val) 0458 self.assert_spec_init(_Monset_spec) 0459 0460 0461 def __repr__ (self): 0462 0463 elfmt = ", ".join(repr(x) for x in self.__dict__["*"]) 0464 return "%s([%s])" % (self.__class__.__name__, elfmt) 0465 0466 0467 def __str__ (self): 0468 0469 return self.__repr__() 0470 0471 0472 def __len__ (self): 0473 0474 return len(self.__dict__["*"]) 0475 0476 0477 def __iter__ (self): 0478 0479 return iter(self.__dict__["*"]) 0480 0481 0482 def __eq__ (self, other): 0483 0484 if len(self.__dict__["*"]) != len(other): 0485 return False 0486 for i in range(len(other)): 0487 if self.__dict__["*"][i] not in other: 0488 return False 0489 return True 0490 0491 0492 def __ne__ (self, other): 0493 0494 return not self.__eq__(other) 0495 0496 0497 def __contains__ (self, val): 0498 0499 return val in self.__dict__["*"] 0500 0501 0502 def add (self, val): 0503 0504 self.assert_spec_setitem(val) 0505 if val not in self.__dict__["*"]: 0506 self.__dict__["*"].append(val) 0507 self.__dict__["#"]["*"] += 1 0508 0509 0510 def remove (self, val): 0511 0512 self.assert_spec_setitem(val) 0513 if val in self.__dict__["*"]: 0514 self.__dict__["*"].remove(val) 0515 self.__dict__["#"]["*"] += 1 0516 0517 0518 def items (self): 0519 0520 return list(self.__dict__["*"]) 0521