Updated question, see below
I'm starting a new project and I would like to experiment with components based architecture (I chose PyProtocols). It's a little program to display and interract with realtime graphics.
I started by designing the user input components:
- IInputDevice - e.g. a mouse, keyboard, etc... An InputDevice may have one or more output channels:
- IOutput - an output channel containing a single value (e.g. the value of a MIDI slider)
- ISequenceOutput - an output channel containing a sequence of values (e.g. 2 integers representing mouse position)
- IDictOutput - an output channel containing named values (e.g. the state of each key of the keyboard, indexed by keyboard symbols)
Now I would like to define interfaces to filter those outputs (smooth, jitter, invert, etc...).
My first approach was to create an InputFilter interface, that had different filter methods for each kind of output channel it was connected to... But the introduction in PyProtocols documentation clearly says that the whole interface and adapters thing is about avoiding type checking !
So my guess is that my InputFilter interfaces should look like this:
- IInputFilter - filters IOutput
- ISequenceInputFilter - filters ISequenceOutput
- IDictInputFilter - filters IDictOutput
Then I could have a connect() method in the I*Ouptut interfaces, that could magically adapt my filters and use the one appropriate for the type of output.
I tried to implement that, and it kind of works:
class InputFilter(object):
"""
Basic InputFilter implementation.
"""
advise(
instancesProvide=[IInputFilter],
)
def __init__(self):
self.parameters = {}
def connect(self, src):
self.src = src
def read(self):
return self.src.read()
class InvertInputFilter(InputFilter):
"""
A filter inverting single values.
"""
def read(self):
return -self.src.read()
class InvertSequenceInputFilter(InputFilter):
"""
A filter inverting sequences of values.
"""
advise(
instancesProvide=[ISequenceInputFilter],
asAdapterForProtocols=[IInputFilter],
)
def __init__(self, ob):
self.ob = ob
def read(self):
res = []
for value in self.src.read():
res.append(-value)
return res
Now I can adapt my filters to the type of output:
filter = InvertInputFilter()
single_filter = IInputFilter(filter) # noop
sequence_filter = ISequenceInputFilter(filter) # creates an InvertSequenceInputFilter instance
single_filter and sequence_filter have the correct behaviors and produce single and sequence data types. Now if I define a new InputFilter type on the same model, I get errors like this:
TypeError: ('Ambiguous adapter choice', <class 'InvertSequenceInputFilter'>, <class 'SomeOtherSequenceInputFilter'>, 1, 1)
I must be doing something terribly wrong, is my design even correct ? Or maybe am I missing the point on how to implement my InputFilterS ?
Update 2
I understand I was expecting a little too much magic here, adapters don't type check the objects they are adapting and just look at the interface they provide, which now sounds normal to me (remember I'm new to these concepts !).
So I came up with a new design (stripped to the bare minimum and omitted the dict interfaces):
class IInputFilter(Interface):
def read():
pass
def connect(src):
pass
class ISingleInputFilter(Interface):
def read_single():
pass
class ISequenceInputFilter(Interface):
def read_sequence():
pass
So IInputFilter is now a sort of generic component, the one that is actually used, ISingleInputFilter and ISequenceInputFilter provide the specialized implementations. Now I can write adapters from the specialized to the generic interfaces:
class SingleInputFilterAsInputFilter(object):
advise(
instancesProvide=[IInputFilter],
asAdapterForProtocols=[ISingleInputFilter],
)
def __init__(self, ob):
self.read = ob.read_single
class SequenceInputFilterAsInputFilter(object):
advise(
instancesProvide=[IInputFilter],
asAdapterForProtocols=[ISequenceInputFilter],
)
def __init__(self, ob):
self.read = ob.read_sequence
Now I write my InvertInputFilter like this:
class InvertInputFilter(object):
advise(
instancesProvide=[
ISingleInputFilter,
ISequenceInputFilter
]
)
def read_single(self):
# Return single value inverted
def read_sequence(self):
# Return sequence of inverted values
And to use it with the various output types I would do:
filter = InvertInputFilter()
single_filter = SingleInputFilterAsInputFilter(filter)
sequence_filter = SequenceInputFilterAsInputFilter(filter)
But, again, this fails miserably with the same kind of error, and this time it's triggered directly by the InvertInputFilter definition:
TypeError: ('Ambiguous adapter choice', <class 'SingleInputFilterAsInputFilter'>, <class 'SequenceInputFilterAsInputFilter'>, 2, 2)
(the error disapears as soon as I put exactly one interface in the class' instancesProvide clause)
Update 3
After some discussion on the PEAK mailing list, it seems that this last error is due to a design flaw in PyProtocols, that does some extra checks at declaration time. I rewrote everything with zope.interface and it works perfectly.