I've spent a whole afternoon figuring out how to add a deprecation warning when using a constant attached to a Python module, so here's how to do it in case you would need to do something similar.
The use case is the following : as GDAL RFC 49 introduces curve geometries, I wanted the user to be warned if he uses the now deprecated ogr.wkb25DBit constant, since the new geometry types no longer use the most significant bit of the value to indicate the Z dimension.
from osgeo import ogr
ogr.py:167: DeprecationWarning: deprecated: use ogr.GT_Flatten(), ogr.GT_HasZ() or ogr.GT_SetZ() instead
warnings.warn("deprecated: use ogr.GT_Flatten(), ogr.GT_HasZ() or ogr.GT_SetZ() instead", DeprecationWarning)
The issue is that ogr.wkb25DBit was a variable assigned to a module, and you cannot do anything to "attach" a function that would be evaluated at runtime when the constant is used.
After several non conclusive attempts, the solution finally came from this article. Basically, you can use the sys.modules dictionnary to substitute the original module by a class instance (called pseudo-module class instance in the rest of this writing). And on the class of this class instance, you can define a property that will call a function when it is read. To get all other global functions and classes of the original module, you can copy the global dictionnary of the module to the pseudo-module class instance, and here you are ! If this summary does not make sense, the above mentionned article explains that in greater details.
My personal touch is a further improvement. The above trick is nice, but when from the Python console, you call help(ogr), it displays the help of the pseudo-module class instance, and not the one of the original module. So you loose the help of all other constants, functions and classes. Almost everything in fact.
But even Python builtins functions like help() can be replaced by a custom version. The custom help() tests if the object passed is the pseudo-module class instance, in which case it substitutes it temporarily with the original module before calling the original help().
Apart from issuing deprecation warnings, such a technique can be usefull to make module constants really constants. Did you know that you can affect another value to math.pi... ?
The full code for all above tricks (works with all Python 2 and 3 versions starting with 2.4) :
# Original module constant
my_constant = 1
# Backup original dictionnary before doing anything else
_initial_dict = globals().copy()
warnings.warn("my_constant is deprecated", DeprecationWarning)
# Inspired from http://www.dr-josiah.com/2013/12/properties-on-python-modules.html
self.__dict__ = globals()
self._initial_dict = _initial_dict
# Transfer properties from the object to the Class
for k, v in list(self.__dict__.items()):
if isinstance(v, property):
setattr(self.__class__, k, v)
# Replace original module by our object
self._original_module = sys.modules[self.__name__]
sys.modules[self.__name__] = self
# Custom help() replacement to display the help of the original module
# instead of the one of our instance object
def __init__(self, module):
self.module = module
self.original_help = help
# Replace builtin help by ours
import __builtin__ as builtins # Python 2
import builtins # Python 3
builtins.help = self
def __call__(self, *args, **kwds):
if args == (self.module,):
# Restore original module before calling help() otherwise
# we don't get methods or classes mentionned
sys.modules[self.module.__name__] = self.module._original_module
ret = self.original_help(self.module._original_module, **kwds)
# Reinstall our module
sys.modules[self.module.__name__] = self.module
elif args == (self,):
return self.original_help(self.original_help, **kwds)
return self.original_help(*args, **kwds)