It feels like I am missing the Shibboleth, so I hope the answer to my question below will be: you are looking for the {pattern} name.
I am working on a Python (3.10) project where I want to allow plugin like extensibility that would allow for two way communication between modules in the same project.
Imagine a two way pub/sub pattern without asynchronous messaging. Coming from OOP I would call it a dynamic dispatch and implement it with a little bit of reflection.
Unfortunately for me, it appears that dynamic dispatch
has very specific meaning in Python and it relates to implementing polymorphic like behaviour.
A very simple example of what I want to achieve is this. Imagine there are three modules/classes/files in a project.
# somewhere in module 1
def option_one(self):
return 'first option'
# somewhere in module 2
def option_two(self):
return 'second option'
# somewhere in module 3
def options(self):
# implementation of some patter
# name I cannot figure out
# eventually execute it from here
print(options())
The result should be that the print out is:
[ 'first option', 'second option' ]
The important part is, that there should be no need to hard code in module 3
anything that would imply ahead of time knowledge of the specific implementations.
So far I have been experimenting with the Registry pattern and subclassing, and it works with single method, but I need to be able to define multiple methods some of which may, or may not be implemented.
My current attempt looks like so, but it does feel like I am missing something.
class PluginBase:
instances = []
# modified registry pattern that instantiates subclasses
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.instances.append(cls())
# the decorator to turn functions into 'dispatch'
def query(func: callable) -> callable:
def wrapper():
results = []
for i in PluginBase.instances:
fn = func.__name__
if getattr(i, fn, None) \
and not getattr(PluginBase, fn).__qualname__==getattr(i, fn).__qualname__:
try:
results += PluginBase.always_array(getattr(i, fn)())
except Exception as err:
print(err)
pass
return results
return wrapper
@staticmethod
def always_array(value: any) -> list:
if type(value) == list:
return value
else:
return [value]
@query
def options() -> list:
return []
class First(PluginBase):
def options(self) -> any:
return 'from first'
class Second(PluginBase):
def options(self) -> any:
return ['from second']
# This produces the desired output
print(PluginBase.options())
What am I missing? What is the Shibboleth, the word that unlocks it all?
Aucun commentaire:
Enregistrer un commentaire