I am designing a library that has adapters that supports a wide-range of libraries. I want the library to dynamically choose which ever adapter that has the library it uses installed on the machine when importing specific classes.
The goal is to be able to change the library that the program depends on without having to make modifications to the code. This particular feature is for handling RabbitMQ connections, as we have had a lot of problems with pika, we want to be able to change to a different library e.g. pyAMPQ or rabbitpy without having to change the underlying code.
I was thinking of implementing something like this in the __init__.py
file of servicelibrary.simple
.
try:
#import pika # Is pika installed?
from servicelibrary.simple.synchronous import Publisher
from servicelibrary.simple.synchronous import Consumer
except ImportError:
#import ampq # Is ampq installed?
from servicelibrary.simple.alternative import Publisher
from servicelibrary.simple.alternative import Consumer
Then when the user imports the library
from servicelibrary.simple import Publisher
The underlying layer looks something like this
alternative.py
import amqp
class Publisher(object):
......
class Consumer(object):
......
synchronous.py
import pika
class Publisher(object):
......
class Consumer(object):
......
This would automatically pick the second one when the first one is not installed.
Is there a better way of implementing something like this? If anyone could link a library/adapter with a similar implementation that would be helpful as well.
[Edit]
What would be the cleanest way to implement something like this? In the future I would also like to be able to change the default preference. Ultimately I may just settle for using the library installed, as I can control that, but it would be a nice feature to have.
Alexanders suggestion is interesting, but I would like to know if there is a cleaner way.
[Edit2]
The original example was simplified. Each module may contain multiple types of imports, e.g. Consumer and Publisher.
I know two method, one is wildly used and another is my guesswork. You can choose one for your situation.
The first one, which is widely used, such as
from tornado.concurrent import Future
.Then you can use
from tornado.concurrent import Future
in other files.The second one, which is my guesswork, and I write simple demo, but I haven't use it in production environment because I don't need it.
You can run the script before other script
import servicelibrary.simple.synchronous
. Then you can use the script as before:The only thing I wonder is that what are the
consequences
of my guesswork.The importlib.import_module might do what you need:
I guess, this is not the most advance technique, but the idea should be clear. And you can take a look at the imp module as well.
Based on the answers I ended up with the following implementation for Python 2.7.
Examples are simplified for stackoverflow..
Although, as I also had some of my projects running Python 2.6 I had to either modify the code, or include importlib. The problem with a production platform is that it isn't always easy to include new dependencies.
This is the compromise I came up with, based on
__import__
instead ofimportlib
.It might be worth checking if
sys.modules
actually contains the namespace so you don't get aKeyError
raised, but it is unlikely.You've got the right idea. Your case works because each subobject has the same sort of classes e.g. both APIs have a class called
Publisher
and you can just make sure the correct version is imported.If this isn't true (if possible implementation A and B are not similar) you write your own facade, which is just your own simple API that then calls the real API with the correct methods/parameters for that library.
Obviously switching between choices may require some overhead (i don't know your case, but for instance, let's say you had two libraries to walk through an open file, and the library handles opening the file. You can't just switch to the second library in the middle of the file and expect it to start where the first library stopped). But it's just a matter of saving it:
etc.
A flexible solution, using
importlib
. This is a complete, working solution that i've tested.First, the header:
We import the required module, set our indicator, and specify our modules.
modules
is a dictionary, with the key set as the default module, and the value as a list of alternatives.Next, the import-ant part:
And to have the classes, simply do:
With this solution, you can have multiple alternatives at once. For example, you can use both rabbitpy and pyAMPQ as your alternatives.
Note: Works with both Python 2 and Python 3.
If you have more questions, feel free to comment and ask!