File indexing completed on 2024-04-21 16:29:16

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