123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- import sys
- import odml
- import odml.tools.event as event
- import odml.tools.weakmeth as weakmeth
- import odml.tools.nodes as nodes
- # events are required for proxy objects
- odml.setMinimumImplementation('event')
- class Proxy(object):
- """common interface"""
- pass
- # here we hook the base-class-comparism functionality to
- # prefer comparism provided by the proxy obj
- _odml_base___eq__ = odml.base.baseobject.__eq__
- def _proxy___eq__(self, obj):
- if isinstance(obj, Proxy) and not isinstance(self, Proxy):
- return obj == self
- return _odml_base___eq__(self, obj)
- odml.base.baseobject.__eq__ = _proxy___eq__
- class BaseProxy(Proxy):
- """
- A nonfunctional proxy, having only the delegate
- self._proxy_obj
- Comparism is hooked, so the following always holds
- >>> BaseProxy(o) == o
- True
- """
- def __init__(self, obj):
- object.__setattr__(self, "_proxy_obj", obj)
- def __eq__(self, obj):
- """
- return True if both reference to equal objects
- or there referenced object equals *obj*
- """
- if not isinstance(obj, BaseProxy):
- return self._proxy_obj == obj
- return self._proxy_obj == obj._proxy_obj
- def __neq__(self, obj):
- return not self == obj
- def __repr__(self):
- return "P(%s)" % repr(self._proxy_obj)
- class EqualityBaseProxy(BaseProxy):
- """
- A BaseProxy that uses a set of attributes (defined in self._format)
- to compare to other objects
- """
- def __eq__(self, obj):
- for key in self._format:
- if not hasattr(self, key) or not hasattr(obj, key):
- return False
- if getattr(self, key) != getattr(obj, key):
- return False
- return True
- class PassProxy(BaseProxy):
- """
- A simple Proxy forwarding all attribute access to the delegate
- It can have inaccessable values (write protected)::
- inaccessable = {'name': 'default name'}
- Attributes indicated in the dictionary *personal* are stored in the proxy
- and not in the delegate, however note: functions not defined in the proxy
- are actually attributes of the delegate and executed on it, so they will not
- work on the *personal* attributes, but on the delegates ones.
- """
- PASS_THROUGH = "PASS_THROUGH"
- inaccessable = {}
- personal = set()
- def __getattr__(self, name):
- val = self.inaccessable.get(name, self.PASS_THROUGH)
- if val != self.PASS_THROUGH:
- return val
- return getattr(self._proxy_obj, name)
- def __hasattr__(self, name):
- return getattr(self._proxy_obj, name)
- def __setattr__(self, name, value):
- if name in self.inaccessable:
- raise AttributeError("Cannot set '%s' attribute on Proxy object" % name)
- if name in self.personal:
- return object.__setattr__(self, name, value)
- return setattr(self._proxy_obj, name, value)
- def __len__(self):
- return self._proxy_obj.__len__()
- class NotifyingProxy(event.ModificationNotifier, PassProxy):
- """
- Hooks the personal attributes to cause change events
- """
- def __setattr__(self, name, value):
- if name in self.personal:
- return super(NotifyingProxy, self).__setattr__(name, value)
- return PassProxy.__setattr__(self, name, value)
- class HookProxy(BaseProxy):
- """
- A proxy that only intercepts certain attributes
- """
- hooks = set()
- def __getattribute__(self, name):
- if name in object.__getattribute__(self, "hooks"):
- return self._proxy_obj.__getattribute__(name)
- return super(HookProxy, self).__getattribute__(name)
- def __setattr__(self, name, value):
- if name in object.__getattribute__(self, "hooks"):
- setattr(self._proxy_obj, name, value)
- else:
- super(HookProxy, self).__setattr__(name, value)
- class ChangeAbleProxy(NotifyingProxy):
- """
- A notifying PassProxy with a change handler, attached to original object
- and passing events on modifying them in such that they appear to originate
- from the Proxy object.
- Change handlers are installed as weak references, s.t. the installed change
- handlers don't solely cause the Proxy object to stay in memory
- """
- personal = set(["_change_handler"])
- _change_handler = None
- def __init__(self, obj):
- super(ChangeAbleProxy, self).__init__(obj)
- # use a weak ref and detach at some point
- weak_change_handler = weakmeth.WeakMethod(self.change_handler)
- def call(context):
- if weak_change_handler() is None:
- return
- return weak_change_handler()(context)
- self._weak_change_handler = call
- if hasattr(obj, "add_change_handler"):
- obj.add_change_handler(call)
- @property
- def _Changed(self):
- return self._proxy_obj._Changed
- def change_handler(self, context):
- """
- redo the change handler but replace the proxied object
- with our instance
- """
- cur = context._obj.pop()
- context.passOn(self)
- context._obj.append(cur)
- def __del__(self):
- """
- our object is no longer needed, so we can also remove the change handler
- from the original object
- """
- if hasattr(self._proxy_obj, "remove_change_handler"):
- self._proxy_obj.remove_change_handler(self._weak_change_handler)
- class PropertyProxy(EqualityBaseProxy, ChangeAbleProxy, odml.property.Property, nodes.PropertyNode):
- """
- A PropertyProxy provides transparent access to a Property,
- blocking only the 'mapping' attribute.
- It has however to take care, that when accessing its values, appropriate
- proxy objects are returned, as otherwise gui functionality breaks
- """
- inaccessable = {"mapping": None}#, "name": PassProxy.PASS_THROUGH}
- personal = set(["_section", "name", "_p_values"]) | ChangeAbleProxy.personal
- _Changed = event.Property._Changed
- _p_values = []
- # TODO make this generic
- @property
- def parent(self):
- return self._section
- @property
- def values(self):
- """
- provide transparent access to values as Proxy objects
- use "caching" to return the same proxy object for the same value
- as other functionality needs to be able to identify a certain object
- exactly (TODO append/insert/remove invalidates the cache and might still
- break stuff)
- """
- # do some caching
- if len(self._p_values) == len(self._proxy_obj.values):
- cached = True
- for i, val in enumerate(self._p_values):
- if val._proxy_obj is not self._proxy_obj.values[i]:
- cached = False
- break
- if cached:
- return self._p_values
- # transparently create proxy objects
- self._p_values = list(map(ValueProxy, self._proxy_obj.values))
- for i in self._p_values: # and make them remember to which property they belong
- i._property = self
- return self._p_values
- def change_handler(self, context):
- """
- we need to fix append/insert/remove notifications
- so that they deal with ProxyObjects instead
- """
- # TODO this needs to be double checked
- oval = None
- if isinstance(context.val, odml.value.Value):
- oval = context.val
- if context.postChange ^ (context.action == "remove"):
- assert oval.parent is not None
- i = self.values.index(oval)
- context.val = self.values[i]
- else: # the val does not yet/anymore exist
- assert oval.parent is None
- context.val = ValueProxy(oval)
- context.val._property = self
- super(PropertyProxy, self).change_handler(context)
- if oval is not None:
- context.val = oval
- def path_to(self, child):
- """
- override this, to account for proxy objects
- """
- if not isinstance(child, PassProxy):
- return super(PropertyProxy, self).path_to(child)
- for (i, obj) in enumerate(self._values):
- if obj is child._proxy_obj: return (i,)
- raise ValueError("%s does not contain the item %s" % (repr(self), repr(child)))
- class ValueProxy(EqualityBaseProxy, ChangeAbleProxy, odml.value.Value, nodes.ValueNode):
- """
- A ValueProxy provides transparent access to a Value,
- but has its own parent (_property)
- """
- personal = set(["_property"]) | ChangeAbleProxy.personal
- _Changed = event.Value._Changed
- # TODO make this generic
- @property
- def parent(self):
- return self._property
- class ReadOnlyObject(object):
- """
- An odml-object whose properties (according to *_format*)
- cannot be modified if *enable_protection* is set.
- """
- enable_protection = False
- def __setattr__(self, name, value):
- if self.enable_protection and name in self._format._args:
- raise AttributeError("attribute '%s' is read-only" % name)
- object.__setattr__(self, name, value)
- class SectionProxy(odml.getImplementation().Section, Proxy):
- """
- A Section that will also pass as a Proxy
- """
- def proxy_remove(self, obj):
- """
- explicitly remove a proxy object without side-effects
- """
- assert isinstance(obj, Proxy)
- super(SectionProxy, self).remove(obj)
- def proxy_append(self, obj):
- """
- explicitly append a proxy object without side-effects
- """
- assert isinstance(obj, Proxy)
- super(SectionProxy, self).append(obj)
- class BaseSectionProxy(SectionProxy):
- pass
- class ReadOnlySection(SectionProxy, ReadOnlyObject):
- """
- A protected section, that cannot be modified
- """
- # @property
- # def sections(self):
- # return []
- # TODO make this generic
- @property
- def parent(self):
- return self._parent
- def append(self, obj):
- if self.enable_protection:
- raise TypeError("Cannot append to a proxy section")
- super(SectionProxy, self).append(obj)
- def insert(self, index, obj):
- raise TypeError("Cannot insert into a proxy section")
- def remove(self, obj):
- if self.enable_protection:
- raise TypeError("Cannot remove from a proxy section")
- super(SectionProxy, self).remove(obj)
- def _unprotected(self, func):
- """
- briefly disable write protection for the execution
- of *func*
- """
- p = self.enable_protection
- self.enable_protection = False
- ret = func()
- self.enable_protection = p
- return ret
- class NonexistantSection(ReadOnlySection):
- """
- A NonexistantSection is a fake section that only exists
- in the mapping. However it has real children.
- """
- # there is an explicit method now: proxy_append
- # def append(self, obj):
- # # always allow to insert proxy objects
- # if isinstance(obj, Proxy):
- # super(SectionProxy, self).append(obj)
- # else:
- # super(NonexistantSection, self).append(obj)
- # def proxy_remove(self, obj):
- # # also allow to remove proxy objects
- # # unmap the original property and then remove it (this is done in mapping)
- # #if not isinstance(obj, PropertyProxy):
- # return super(NonexistantSection, self).remove(obj)
- #
- # raise NotImplementedError("""
- # we could do this, but this would break undo functionality, so
- # better remove the original property from its parent section
- # """)
- # # prop = obj._proxy_objremove_proxy
- # # mapping.unmap_property(prop=prop, mprop=obj)
- # # prop.parent.remove(prop)
- def merge(self, section=None):
- """
- performs the merge operation, briefly disables read_protection
- """
- self._unprotected(lambda: super(NonexistantSection, self).merge(section))
- def unmerge(self, section):
- """
- performs the unmerge operation, briefly disables read_protection
- """
- self._unprotected(lambda: super(NonexistantSection, self).unmerge(section))
- def __repr__(self):
- return super(NonexistantSection, self).__repr__().replace("<Section", "<?Section")
- class DocumentProxy(EqualityBaseProxy, odml.getImplementation().Document):
- """
- A bare bones DocumentProxy not yet proxing actually much. TODO make it a HookProxy?
- """
- def __init__(self, doc):
- super(DocumentProxy, self).__init__(doc)
- odml.getImplementation().Document.__init__(self) # TODO this might already be a different implementation now. and we're not an instance
- for name in odml.format.Document:
- if name in odml.format.Document._args: # skip 'sections'
- setattr(self, name, getattr(doc, name))
- # TODO append also needs to append to original document and care about mappings and stuff
- # TODO same for insert/remove
- def proxy_remove(self, obj):
- self.remove(obj)
- def proxy_append(self, obj):
- self.append(obj)
- #class DocumentProxy(EqualityBaseProxy, ChangeAbleProxy, odml.document.Document, nodes.DocumentNode):
- # """
- # A ValueProxy provides transparent access to a Value
- # """
- # personal = set(["_sections"]) | ChangeAbleProxy.personal
- # _Changed = event.Document._Changed
- #
- # # TODO make this generic
- # @property
- # def sections(self):
- # return self._sections
- #
- # def proxy_remove(self, obj):
- # self.remove(obj)
- #
- # def proxy_append(self, obj):
- # self.append(obj)
- class MappedSection(EqualityBaseProxy, HookProxy, ReadOnlySection):
- """
- Like a fake section, but we reference a concrete section
- and forward changes to it
- TODO stuff works, but clear to account for proxy_append/insert/remove
- """
- hooks = set(["name"])
- def __init__(self, obj, template=None):
- super(MappedSection, self).__init__(obj)
- if template is not None:
- obj = template
- super(ReadOnlySection, self).__init__(obj.name, obj.type)
- if hasattr(obj, "add_change_handler"):
- obj.add_change_handler(self.change_handler)
- def change_handler(self, context):
- if context.postChange and context.obj is context.cur:
- # intercept append and remove actions
- if context.action == "append" or context.action == "insert":
- self.proxy_append(self.mkproxy(context.val))
- return # don't pass this event further
- elif context.action == "remove":
- obj = self.contains(context.val) # find an equal one
- # the source section removed the object
- # if it was explicitly mapped, it might already be gone
- # if apparently not: handle it
- if obj is not None:
- self.proxy_remove(obj)
- # don't pass this event further: either it does not affect us anyway
- # (removing the mapping causes a seperate change event for this section)
- # or we explicitly remove the obj ourselves causing a seperate event too
- return
- # TODO this could cause conflicts in gui (maybe)
- # pass on the event replacing the original object with us (the proxy equivalent)
- cur = context._obj.pop()
- context.passOn(self)
- context._obj.append(cur)
- def mkproxy(self, obj):
- """
- create a proxy equivalent of obj
- obj is either a Section (-> MappedSection) or a Property (-> PropertyProxy)
- """
- if isinstance(obj, odml.property.Property):
- return PropertyProxy(obj)
- return MappedSection(obj)
- def append(self, obj):
- assert not isinstance(obj, Proxy) # proxies should always go through the proxy_append method. TODO also when setting links?
- # a normal object needs to be appended to the proxied section and then
- # we will handle (hopefully) everything else that might be necessary in
- # the change handler (i.e. append a proxy version to ourselves)
- return self._proxy_obj.append(obj)
- # won't support insert. it'd be crazy
- def remove(self, obj):
- # we should only contain proxy objects (unless a link is present TODO)
- if not isinstance(obj, Proxy):
- print("%s should only contain proxy objects, but then look at this" % repr(self), obj)
- obj = self.contains(obj)
- assert isinstance(obj, Proxy)
- # trying to remove a Section only present in the mapping is prohibited
- if isinstance(obj, NonexistantSection):
- # can't remove what doesn't really exist
- raise TypeError("Cannot remove from a proxy section")
- # remove the mapped object, which may be somewhere else!
- parent = obj._proxy_obj.parent
- if parent is not None:
- parent.remove(obj._proxy_obj)
- try:
- # use the normal remove method of Section
- super(SectionProxy, self).remove(obj)
- except ValueError: # we might have already removed the property in the change handler
- pass
|