[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