[Zope3-checkins]
SVN: Zope3/branches/srichter-blow-services/src/zope/deprecation/
First cut of a deprecation framework.
Stephan Richter
srichter at cosmos.phy.tufts.edu
Fri Jan 14 15:50:28 EST 2005
Log message for revision 28834:
First cut of a deprecation framework.
Changed:
A Zope3/branches/srichter-blow-services/src/zope/deprecation/
A Zope3/branches/srichter-blow-services/src/zope/deprecation/README.txt
A Zope3/branches/srichter-blow-services/src/zope/deprecation/__init__.py
A Zope3/branches/srichter-blow-services/src/zope/deprecation/deprecation.py
A Zope3/branches/srichter-blow-services/src/zope/deprecation/tests.py
-=-
Added: Zope3/branches/srichter-blow-services/src/zope/deprecation/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/deprecation/README.txt 2005-01-14 19:51:08 UTC (rev 28833)
+++ Zope3/branches/srichter-blow-services/src/zope/deprecation/README.txt 2005-01-14 20:50:28 UTC (rev 28834)
@@ -0,0 +1,89 @@
+===============
+Deprecation API
+===============
+
+When we started working on Zope 3.1, we noticed that the hardest part of the
+development process was to ensure backward-compatibility and correctly mark
+deprecated modules, classes, functions, methods and properties. This module
+provides a simple function called `deprecated(names, reason)` to deprecate the
+previously mentioned Python objects.
+
+Let's start with a demonstration of deprecating any name inside a module. To
+demonstrate the functionality, I have placed the following code inside the
+`tests.py` file of this package:
+
+ from zope.deprecation import deprecated
+ demo1 = 1
+ deprecated('demo1', 'demo1 is no more.')
+
+ demo2 = 2
+ deprecated('demo2', 'demo2 is no more.')
+
+ demo3 = 3
+ deprecated('demo3', 'demo3 is no more.')
+
+The first argument to the `deprecated()` function is a list of names that
+should be declared deprecated. If the first argument is a string, it is
+interpreted as one name. The second argument is the reason the particular name
+has been deprecated. It is good practice to also list the version in which the
+name will be removed completely.
+
+Let's now see how the deprecation warnings are displayed.
+
+ >>> from zope.deprecation import tests
+ >>> tests.demo1
+ <string>:1: DeprecationWarning: demo1 is no more
+ 1
+
+ >>> import zope.deprecation.tests
+ >>> zope.deprecation.tests.demo2
+ <string>:1: DeprecationWarning: demo2 is no more
+ 2
+
+You can see that merely importing the affected module or one of its parents
+does not cause a deprecation warning. Only when we try to access the name in
+the module, we get a deprecation warning. On the other hand, if we import the
+name directly, the deprecation warning will be raised immediately.
+
+ >>> from zope.deprecation.tests import demo3
+ <string>:1: DeprecationWarning: demo3 is no more
+
+Also, once a deprecation warning has been displayed, it is not shown again:
+
+ >>> from zope.deprecation import tests
+ >>> tests.demo1
+ 1
+
+New let's see how properties and methods can be deprecated. We are going to
+use the same function as before, except that this time, we do not pass in names
+as first argument, but the method or attribute itself. The function then
+returns a wrapper that sends out a deprecation warning when the attribute or
+method is accessed.
+
+ >>> from zope.deprecation import deprecation
+ >>> class MyComponent(object):
+ ... foo = property(lambda self: 1)
+ ... foo = deprecation.deprecated(foo, 'foo is no more.')
+ ...
+ ... bar = 2
+ ...
+ ... def blah(self):
+ ... return 3
+ ... blah = deprecation.deprecated(blah, 'blah() is no more.')
+ ...
+ ... def splat(self):
+ ... return 4
+
+And here is the result:
+
+ >>> my = MyComponent()
+ >>> my.foo
+ <string>:1: DeprecationWarning: foo is no more.
+ 1
+ >>> my.bar
+ 2
+ >>> my.blah()
+ <string>:1: DeprecationWarning: blah() is no more.
+ 3
+ >>> my.splat()
+ 4
Added: Zope3/branches/srichter-blow-services/src/zope/deprecation/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/deprecation/__init__.py 2005-01-14 19:51:08 UTC (rev 28833)
+++ Zope3/branches/srichter-blow-services/src/zope/deprecation/__init__.py 2005-01-14 20:50:28 UTC (rev 28834)
@@ -0,0 +1,3 @@
+# Make a Python package
+
+from zope.deprecation.deprecation import deprecated
Added: Zope3/branches/srichter-blow-services/src/zope/deprecation/deprecation.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/deprecation/deprecation.py 2005-01-14 19:51:08 UTC (rev 28833)
+++ Zope3/branches/srichter-blow-services/src/zope/deprecation/deprecation.py 2005-01-14 20:50:28 UTC (rev 28834)
@@ -0,0 +1,102 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Deprecation Support
+
+This module provides utilities to ease the development of backward-compatible
+code.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import sys
+import types
+import warnings
+import zope.proxy
+
+class DeprecationProxy(zope.proxy.ProxyBase):
+
+ def __init__(self, module):
+ super(DeprecationProxy, self).__init__(module)
+ self._deprecated = {}
+
+ def deprecate(self, names, message):
+ """Deprecate the given names."""
+ if not isinstance(names, (tuple, list)):
+ names = (names,)
+ for name in names:
+ self._deprecated[name] = message
+
+ def __getattribute__(self, name):
+ if name != '_deprecated' and name in self._deprecated:
+ warnings.warn(self._deprecated[name], DeprecationWarning, 2)
+
+ return super(DeprecationProxy, self).__getattribute__(name)
+
+
+class DeprecatedGetProperty(object):
+
+ def __init__(self, prop, message):
+ self.message = message
+ self.prop = prop
+
+ def __get__(self, inst, klass):
+ warnings.warn(self.message, DeprecationWarning, 2)
+ return self.prop.__get__(inst, klass)
+
+class DeprecatedGetSetProperty(DeprecatedGetProperty):
+
+ def __set__(self, inst, prop):
+ warnings.warn(self.message, DeprecationWarning, 2)
+ self.prop.__set__(inst, prop)
+
+class DeprecatedGetSetDeleteProperty(DeprecatedGetSetProperty):
+
+ def __delete__(self, inst):
+ warnings.warn(self.message, DeprecationWarning, 2)
+ self.prop.__delete__(inst)
+
+def DeprecatedMethod(method, message):
+
+ def deprecated_method(self, *args, **kw):
+ warnings.warn(message, DeprecationWarning, 2)
+ return method(self, *args, **kw)
+
+ return deprecated_method
+
+
+def deprecated(specifier, message):
+ """Deprecate the given names."""
+
+ # We are inside a module
+ locals = sys._getframe(2).f_locals
+ if 'modname' in locals:
+ modname = locals['modname']
+ if not isinstance(sys.modules[modname], DeprecationProxy):
+ sys.modules[modname] = DeprecationProxy(sys.modules[modname])
+ sys.modules[modname].deprecate(specifier, message)
+
+ # ... that means the specifier is a method or attribute of the class
+ if isinstance(specifier, types.FunctionType):
+ return DeprecatedMethod(specifier, message)
+ else:
+ prop = specifier
+ if hasattr(prop, '__get__') and hasattr(prop, '__set__') and \
+ hasattr(prop, '__delete__'):
+ return DeprecatedGetSetDeleteProperty(prop, message)
+ elif hasattr(prop, '__get__') and hasattr(prop, '__set__'):
+ return DeprecatedGetSetProperty(prop, message)
+ elif hasattr(prop, '__get__'):
+ return DeprecatedGetProperty(prop, message)
+
+
Added: Zope3/branches/srichter-blow-services/src/zope/deprecation/tests.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/deprecation/tests.py 2005-01-14 19:51:08 UTC (rev 28833)
+++ Zope3/branches/srichter-blow-services/src/zope/deprecation/tests.py 2005-01-14 20:50:28 UTC (rev 28834)
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Component Architecture Tests
+
+$Id: test_api.py 28632 2004-12-16 17:42:59Z srichter $
+"""
+import sys
+import unittest
+import warnings
+from zope.testing import doctest
+
+# Used in doctests
+from deprecation import deprecated
+demo1 = 1
+deprecated('demo1', 'demo1 is no more.')
+
+demo2 = 2
+deprecated('demo2', 'demo2 is no more.')
+
+demo3 = 3
+deprecated('demo3', 'demo3 is no more.')
+
+
+orig_showwarning = warnings.showwarning
+
+def showwarning(message, category, filename, lineno, file=None):
+ sys.stdout.write(
+ warnings.formatwarning(message, category, filename, lineno))
+
+def setUp(test):
+ warnings.showwarning = showwarning
+
+def tearDown(test):
+ warnings.showwarning = orig_showwarning
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown),
+ ))
+
+if __name__ == "__main__":
+ unittest.main(defaultTest='test_suite')
More information about the Zope3-Checkins
mailing list