Your initial approach is almost exactly what I would advise, except that you allow for both types of objects to exist simultaneously. I would start with a full-blown if
statement in your module, that only allows one of the objects to be defined at a time. Something more like:
if dev_mode():
class MyObject:
# Define deprecated version here
...
else:
class MyObject:
# Define production version here
...
If the difference between the deprecated version and the non-deprecated version is something simple, e.g., that could be easily accomplished with a function or class decorator (like raising a warning), you could simplify the code above to something like:
if dev_mode():
def function_decorator(func, cls=None):
# You can use the second argument when calling manually from a class decorator
name = func.__name__ is cls is None else cls.__name__ + '.' + func.__name__
warnings.warn("Importing deprecated function: {}".format(name))
return func
def class_decorator(cls):
warnings.warn("Importing deprecated class: {}".format(cls.__name__))
# Make additional modifications here (like adding function_decorator to all the class methods)
return cls
else:
def function_decorator(func):
return func
def class_decorator(cls):
return cls
@class_decorator
class MyClass:
pass
Using a module-level if
to avoid multiple versions of the class floating around is the basic tool here. You can add any number of layers of complexity to your process. One technique I have seen for a similar purpose (where the particular version of a class depends on some import-time condition like OS), is to create a package named module1
, and implement two different versions of your classes in different modules entirely. The package structure would look like this:
module1/
|
+-- __init__.py
|
+-- _development.py
|
+-- _production.py
Both _development
and _production
define the same names, but different versions. The underscores in front of the module names imply that they should never be imported directly. You expose module1
as a module rather than as a package using its __init__
file, which would look something like this:
__all__ = ['MyModule']
if dev_mode():
from ._development import MyModule
else:
from ._production import MyModule
If you have a lot of names, you can automate the public import using __all__
in __init__
:
import importlib, sys
__all__ = ['MyClass']
self = sys.modules[__name__]
sub = importlib.import_module('_development' if dev_mode() else '_production')
for name in __all__:
setattr(self, name, getattr(sub, name))
This form of separation allows you to test both the production and the dev versions without having two separate test flows. Your tests can import the private modules directly.