views:

67

answers:

2

I have a fairly simple set of functionality for which I have multiple implementations, e.g., a datastore that could be backed by Redis, MongoDB, or PostgreSQL. How should I structure/write my code so that code that wants to use one of these implementations only needs the dependencies for that implementation, e.g., they don't need to have psycopg2 installed if they are using the Redis backend.

Here's an example. Assume the following module, example.py.

class RedisExample(object):
    try:
        import redis
    except ImportError:
        print("You need to install redis-py.")

    def __init__(self):
        super(RedisExample, self).__init__()

class UnsatisfiedExample(object):
    try:
        import flibbertigibbet
    except ImportError:
        print("You need to install flibbertigibbet-py")

    def __init__(self):
        super(UnsatisfiedExample, self).__init__()

Here's my Python shell experience:

>>> import example
You need to install flibbertigibbet-py

Alternately:

>>> from example import RedisExample
You need to install flibbertigibbet-py

I'd really rather I didn't get that error until I tried to instantiate an UnsatisfiedExample. Is there any sort of common way to approach this problem? I've thought about making example a package with each backend getting its own module and using a factory function, but I wanted to make sure I wasn't missing something better.

Thanks.

+3  A: 

import is just another statement, like for or with. Put it in an if statement, possibly behind an abstraction class.

Ignacio Vazquez-Abrams
I revised the question to provide more detail. Could you post an example of the abstraction class idea you mentioned and how that would address my (hopefully clearer now) concern?
Hank Gay
My answer doesn't change. Perform the import when you need it.
Ignacio Vazquez-Abrams
Are you suggesting that the client code simply do `from example import RedisExample` instead of `import example`? Because that also triggers the `ImportError`.
Hank Gay
... No, I'm suggesting that you import it when you need it. As in, just before you need to access a name in it.
Ignacio Vazquez-Abrams
+4  A: 

Can't you simply put the import statement in the __init__ method of each class? Then it won't be run until you try to make an instance:

class UnsatisfiedExample(object):
    def __init__(self):
        try:
            import flibbertigibbet
        except ImportError:
            raise RuntimeError("You need to install flibbertigibbet-py")
        super(UnsatisfiedExample, self).__init__()
Ian Clelland