Reputation: 26406
Python 3.8
So I have two versions of my software - a standard edition and a pro edition.
I want to be able to have precisely the same code base, but simply by dropping in some additional files, pro features are enabled.
I am doing this by decorating/wrapping the standard version Python functions, so the pro edition functions are seamlessly extending the behaviour of the standard functions.
I've made a simple example of this below.
The fooble.py imports "do_something" from the foo module.
The init.py tries to import the pro functions from test_decorate_pro.py. If it succeeds in the import, then it wraps the standard edition function "do_something" with the pro edition function of the same name "do_something".
All this works.
The problem is that I wanted to NOT have two init.py files. This solution only works if the standard edition has its own init.py that must be overwritten by the new init.py -
standard edition init.py
import sys
from .test_decorate import do_something
So my question is.... is there a solution for this in which I can eliminate the standard edition init.py somehow? I want my pro features to be added by simply dropping in additional files. I don't want to be overwriting anything in the standard edition - I'd like the pro edition to add its features in to the codebase simply by adding, with no overwriting or deleting. And I want there to be no pro edition related code at all in the standard edition codebase, so I don't want to just use the pro edition init.py Hopefully that makes sense.
the directory structure:
fooble.py
foo/__init__.py
foo/test_decorate.py
foo/test_decorate_pro.py
fooble.py:
from foo import do_something
do_something('xxx')
foo/init.py
import sys
from .test_decorate import do_something
try:
import foo.test_decorate_pro
except ImportError as e:
pass
if 'foo.test_decorate_pro' in sys.modules:
do_something = foo.test_decorate_pro.do_something(do_something)
foo/test_decorate.py
def do_something(name):
print(f"STANDARD do something {name}")
foo/test_decorate_pro.py
import wrapt
@wrapt.decorator
def do_something(wrapped, instance, args, kwargs):
print(f"PRO do something args[0]", args, kwargs)
return wrapped(*args, **kwargs)
Upvotes: 0
Views: 375
Reputation: 104792
The only good way to avoid needing to overwrite the __init__.py
file is to put the logic that imports the pro edition (if it's available), in the standard edition. You could simplify this quite a bit if you made the do_something
function from the pro-edition a drop-in replacement of the function of the same name from the standard edition. You can still implement it with a decorator if that makes sense, you'd just go about it differently (and expose fewer details in __init__.py
).
Standard edition test_decorate.py
:
def do_something(name):
print(f"STANDARD do something {name}")
Pro edition test_decorate_pro.py
:
from .test_decorate import do_something as do_something_standard
def do_something(name):
print(f"PRO do something {name}")
do_something_standard(name)
print("done with PRO stuff")
Standard edition __init__.py
file, which should work for the pro edition too:
try:
from .test_decorate_pro import do_something # try to get the pro version first
except ImportError:
from .test_decorate import do_something # fall back to the standard edition
Note that I defined the pro-version of the function directly, without using decorator style code. You don't need to make that change, the only important thing is that the do_stuff
function that you want to expose is the final result of the decoration, not the decorator. There's not really any good reason to use a higher order function like a decorator in this situation, just call the standard version of the function directly, since we can import it from its module in the pro code.
Upvotes: 1