/usr/lib/python2.7/dist-packages/kiwi/model.py is in python-kiwi 1.9.22-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | #
# Kiwi: a Framework and Enhanced Widgets for Python
#
# Copyright (C) 2002-2003, 2005-2006 Async Open Source
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
#
# Author(s): Christian Reis <kiko@async.com.br>
# Johan Dahlin <jdahlin@async.com.br>
#
"""Holds the models part of the Kiwi Framework"""
import os
import pickle
from kiwi import ValueUnset
from kiwi.log import Logger
log = Logger('model')
#
# A model that implements half of an observer pattern; when its
# attributes are changed, it notifies any proxies of the change.
#
class Model:
"""
The Model is a mixin to be used by domain classes when attached to
Proxies. It also provides autonotification of changes to the
attached proxies. Note that if using setters, a specific call to
notify_proxies() may be necessary; see the doc for __setattr__."""
def __init__(self):
self.ensure_init()
def ensure_init(self):
"""
Sets up the variables so the Model's getattr hook and proxy
notification work properly.
"""
# Work around setattr hook. The _v prefixes to the variables let
# the ZODB know that there are non-persistant values. This
# workaround is fine because the API protects them and it
# doesn't affect any other persistence mechanism I know of.
self.__dict__["_v_blocked_proxies"] = []
self.__dict__["_v_proxies"] = {}
self.__dict__["_v_autonotify"] = 1
def disable_autonotify(self):
"""
disable automatic notification to proxies based on __setattr__.
All changes to the model must be followed by a call to
notify_proxies() to allow the proxies to notice the change."""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
self._v_autonotify = 0
def notify_proxies(self, attr):
"""Notify proxies that an attribute value has changed."""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
for proxy in self._v_proxies.get(attr, []):
if proxy not in self._v_blocked_proxies:
proxy.update(attr, ValueUnset, block=True)
def register_proxy_for_attribute(self, attr, proxy):
"""
Attach a proxy to an attribute. The proxy will be notified of
changes to that particular attribute (my means of
Proxy.notify())."""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
# XXX: should use weakref if possible, and if not, warn of leaks
proxies = self._v_proxies
if not proxies.has_key(attr):
proxies[attr] = [proxy]
else:
if proxy in proxies[attr]:
raise AssertionError, ("Tried to attach proxy %s "
"twice to attribute `%s'."
% ( proxy, attr ))
proxies[attr].append(proxy)
def unregister_proxy_for_attribute(self, attr, proxy):
"""Detach a proxy from an attribute."""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
proxies = self._v_proxies
if proxies.has_key(attr) and proxy in proxies[attr]:
# Only one listener per attribute per proxy, so remove()
# works
proxies[attr].remove(proxy)
def unregister_proxy(self, proxy):
"""Deattach a proxy completely from the model"""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
proxies = self._v_proxies
for attribute in proxies.keys():
if proxy in proxies[attribute]:
# Only one listener per attribute per proxy, so remove()
# works
proxies[attribute].remove(proxy)
def flush_proxies(self):
"""Removes all proxies attached to Model"""
self._v_proxies = {}
self._v_blocked_proxies = []
def block_proxy(self, proxy):
"""
Temporarily block a proxy from receiving any notification. See
unblock_proxy()"""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
blocked_proxies = self._v_blocked_proxies
if proxy not in blocked_proxies:
blocked_proxies.append(proxy)
def unblock_proxy(self, proxy):
"""Re-enable notifications to a proxy"""
if not hasattr(self, "_v_proxies"):
self.ensure_init()
blocked_proxies = self._v_blocked_proxies
if proxy in blocked_proxies:
blocked_proxies.remove(proxy)
def __setattr__(self, attr, value):
"""
A special setattr hook that notifies the registered proxies that
the model has changed. Work around it setting attributes
directly to self.__dict__.
Note that setattr() assumes that the name of the attribute being
changed and the proxy attribute are the same. If this is not the
case (as may happen when using setters) you must call
notify_proxies() manually from the subclass' setter.
"""
# XXX: this should be done last, since the proxy notification
# may raise an exception. Or do we ignore this fact?
self.__dict__[attr] = value
if not hasattr(self, "_v_proxies"):
self.ensure_init()
if self._v_autonotify and self._v_proxies.has_key(attr):
self.notify_proxies(attr)
#
# A sample model that pickles itself into a file
#
class PickledModel(Model):
"""
PickledModel is a model that is able to save itself into a pickle
using save(). This has all the limitations of a pickle: its
instance variables must be picklable, or pickle.dump() will raise
exceptions. You can prefix variables with an underscore to make them
non-persistent (and you can restore them accordingly by overriding
__setstate__, but don't forget to call PickledModel.__setstate__)
"""
def __init__(self):
self._filename = None
def __getstate__(self):
"""Gets the state from the instance to be pickled"""
odict = self.__dict__
for key in odict.keys():
if key.startswith("_"):
del odict[key]
return odict
def __setstate__(self, dict):
"""Sets the state to the instance when being unpickled"""
Model.__dict__["__init__"](self)
self.__dict__.update(dict)
def save(self, filename=None):
"""
Saves the instance to a pickle filename. If no filename argument is
provided, will try to use the internal _filename attribute that is
set using set_filename()
@param filename: optional filename to pass in
"""
filename = filename or self._filename
if not filename:
raise AttributeError(
"No pickle specified, don't know where to save myself")
fh = open(filename, "w")
try:
try:
pickle.dump(self, fh)
except pickle.PicklingError, e:
raise AttributeError(
"Tried to pickle an instance variable that isn't "
"supported by pickle.dump(). To work around this, you "
"can prefix the variable name with an underscore "
" and it will be ignored by the pickle machinery "
"in PickledModel. The original error "
"follows:\n\n%s" % e)
finally:
fh.close()
def set_filename(self, filename):
"""
Sets the name of the file which will be used to pickle the
model"""
self._filename = filename
#@unpickle
def unpickle(cls, filename=None):
"""
Loads an instance from a pickle file; if it fails for some reason,
create a new instance.
- filename: the file from which the pickle should be loaded.
If file is not provided, the name of the class suffixed by
".pickle" is used (i.e. "FooClass.pickle" for the
class FooClass).
If the pickle file is damaged, it will be saved with the extension
".err"; if a file with that name also exists, it will use ".err.1"
and so on. This is to avoid the damaged file being clobbered by an
instance calling save() unsuspectingly.
"""
if not filename:
filename = cls.__name__ + ".pickle"
if not os.path.exists(filename):
ret = cls()
ret.set_filename(filename)
return ret
fh = open(filename, "r")
try:
data = fh.read()
ret = pickle.loads(data)
except (EOFError, KeyError):
# save backup of original pickle with an extension of
# .err, .err.1, .err.2, etc.
stem = filename + ".err"
i = 0
backup = stem
while os.path.exists(backup):
i = i + 1
backup = stem + ".%d" % i
open(backup, "w").write(data)
log.warn(
"pickle in %r was broken, saving backup in %r and creating "
"new <%s> instance\n""" % (filename, backup, cls.__name__))
ret = cls()
fh.close()
ret.set_filename(filename)
return ret
unpickle = classmethod(unpickle)
# TODO: implement a Model that saves itself as CSV/XML?
|