enthought / envisage

Envisage is a Python-based framework for building applications whose functionalities can be extended by adding "plug-ins".
http://docs.enthought.com/envisage/
Other
82 stars 26 forks source link

Awkwardness when advertising more than one service instance of the same type #415

Open kitchoi opened 3 years ago

kitchoi commented 3 years ago

This issue describes the awkwardness one encounters when two instances of the same type need to be shared across plugins using the service mechanism. (This awkwardness also applies if one must advertise multiple services of the same type even if those services are to be used by the same plugin that advertises them.)

This issue serves to present the problem. This may motivate an alternative design for one plugin to share resources with another plugin.

Starting from this example: https://github.com/enthought/envisage/blob/28f7958f9db522ce398397a0c09bf6989d8be1a1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz_plugin.py#L29-L40

Suppose now we'd like to have two Lorenz instances in the application, then one could define two ServiceOffer with different factories, with the same protocol, e.g.:

     lorenz_service_offer_1 = ServiceOffer( 
         protocol="acme.lorenz.lorenz.Lorenz", 
         factory=some_factory_1, 
     ) 
     lorenz_service_offer_2 = ServiceOffer( 
         protocol="acme.lorenz.lorenz.Lorenz", 
         factory=some_factory_2, 
     ) 

The two service instances may have slightly different behaviours, and those differences are important where they are used.

Ambiguity when obtaining a service from get_service With that, existing usage of application.get_service(Lorenz) would obtain the instance from some_factory_1 assuming the fact that Python dictionary are ordered and assuming the service offers are contributed in the order of [lorenz_service_offer_1, lorenz_service_offer_2]. If the two offers are contributed by separate plugins, then the order depends on the ordering of the plugins too.

407 is related here

Existing ways to obtain the second service is fragile or not possible To obtain the second one, one will need to use get_services with some additional filtering, e.g.: https://github.com/enthought/envisage/blob/d62af08e75c5555e69c4fc4688393dee737680e8/envisage/tests/test_service_registry.py#L291 That logic depends on eval, here: https://github.com/enthought/envisage/blob/d62af08e75c5555e69c4fc4688393dee737680e8/envisage/service_registry.py#L215

Workaround is also awkward Suppose we cannot compose a query for get_services or we want to avoid the fragility mentioned above, then one way to workaround this would be to create a subclass of the service, e.g.:

class Lozent1(acme.lorenz.lorenz.Lorenz):
 pass

class Lozent2(acme.lorenz.lorenz.Lorenz):
 pass

And then advertise the services:

     lorenz_service_offer_1 = ServiceOffer( 
         protocol=Lozent1, 
         factory=some_factory_1, 
     ) 
     lorenz_service_offer_2 = ServiceOffer( 
         protocol=Lozent2, 
         factory=some_factory_2, 
     ) 

Part of this is imposed by the fact that protocol must be either a type or a string that can be used for importing a type.