Deprecate code#
This chapter describes how to enable deprecation warnings and best practices for deprecating code in Plone, Zope, and Python.
See also
For background on deprecation philosophy and use cases, see Deprecation.
Enable deprecation warnings#
This section describes how to enable deprecation warnings in Python, both in an interpreter and code, and when running tests.
Python#
- Enable warnings
Warnings are written to
stderrby default, butDeprecationWarningoutput is surpressed by default.Output can be enabled by starting the Python interpreter with the -W[all|module|once] argument.
As an alternative, the environment variable
PYTHONWARNINGScan be set todefault, in other words,PYTHONWARNINGS=default.It's possible to enable output in code, too.
import warnings warnings.simplefilter("module")
- Configure logging
Once output is enabled, it's possible to use
logging.captureWarnings()to redirect warnings to the logger.import logging logging.captureWarnings(True)
Running tests#
In Plone, test deprecation warnings are not shown by default.
The zope.conf setting is not taken into account.
To enable deprecation warnings, use the -W command.
Given you're using a modern buildout with a virtual environment as recommended, the command would be the following.
./bin/python -W module ./bin/test
Deprecation best practices#
It's recommended to follow these best practices when deprecating code.
Vanilla deprecation messages#
Python offers a built-in exception DeprecationWarning which can be issued using the standard library's warnings module.
Its basic usage is the following example.
import warnings
warnings.warn("deprecated", DeprecationWarning)
Move an entire module#
Given a package old.pkg with a module foo.py, to move it to a package new.pkg as bar.py, go through the following steps.
zope.deprecation Moving modules offers a helper.
Move the
foo.pyasbar.pyto thenew.pkg.At the old place, create a new
foo.py, and add to it the following lines of code.from zope.deprecation import moved moved("new.pkg.bar", "Version 2.0")
Now you can still import the module at the old place, but get a deprecation warning.
DeprecationWarning: old.pkg.foo has moved to new.pkg.bar. Import of old.pkg.foo will become unsupported in Version 2.0
Move an entire package#
To move an entire package, the process is exactly the same as moving a module, but instead, create a file for each module in the package.
Deprecate methods and properties#
Use the @deprecate decorator from zope.deprecation Deprecating methods and properties to deprecate methods in a module.
from zope.deprecation import deprecate
@deprecate("Old method is no longer supported, use new_method instead.")
def old_method():
return "some value"
The @deprecated wrapper method deprecates properties.
from zope.deprecation import deprecated
foo = None
foo = deprecated(foo, "foo is no more, use bar instead")
Move functions and classes#
This example describes how to move some classes or functions from a Python file at old/foo/bar.py to new/baz/baaz.py.
Here, zope.deferredimport offers a deprecation helper.
It also avoids circular imports at initialization time.
import zope.deferredimport
zope.deferredimport.initialize()
zope.deferredimport.deprecated(
"Import from new.baz.baaz instead",
SomeOldClass="new.baz:baaz.SomeMovedClass",
some_old_function="new.baz:baaz.some_moved_function",
)
def some_function_which_is_not_touched_at_all():
pass
Deprecate a GenericSetup profile#
In GenericSetup, the post_handler attribute in ZCML can be used to call a function after the profile was applied.
Use this feature to issue a warning.
First, register the same profile twice, under both the new name and old.
<genericsetup:registerProfile
name="default"
title="My Fancy Package"
directory="profiles/default"
description="..."
provides="Products.GenericSetup.interfaces.EXTENSION"
/>
<genericsetup:registerProfile
name="some_confusing_name"
title="My Fancy Package (deprecated)"
directory="profiles/some_confusing_name"
description="... (use profile default instead)"
provides="Products.GenericSetup.interfaces.EXTENSION"
post_handler=".setuphandlers.deprecate_profile_some_confusing_name"
/>
Then in setuphandlers.py, add a function.
import warnings
def deprecate_profile_some_confusing_name(tool):
warnings.warn(
'The profile with id "some_confusing_name" was renamed to "default".',
DeprecationWarning
)
Deprecate a template location#
Sometimes you need to move templates to new locations.
Since add-ons often use z3c.jbot to override templates by their location, you'll need to point them to the new location as well as make sure that the override still works with the old location.
To deprecate a template, follow these steps.
In the old package folder's
__init__.py, add a dictionaryjbot_deprecationsthat maps the old template locations to their new counterparts.jbot_deprecations = { "plone.locking.browser.info.pt": "plone.app.layout.viewlets.info.pt" }
Add this deprecation snippet to the package
configure.zcmlfile.1<configure 2 xmlns="http://namespaces.zope.org/zope" 3 xmlns:browser="http://namespaces.zope.org/browser" 4 xmlns:zcml="http://namespaces.zope.org/zcml" 5 > 6 7 <include 8 package="z3c.jbot" 9 file="meta.zcml" 10 zcml:condition="installed z3c.jbot" 11 /> 12 <browser:jbotDeprecated 13 zcml:condition="have jbot-deprecations" 14 dictionary=".jbot_deprecations" 15 /> 16 17</configure>
If a z3c.jbot version that supports deprecation is found, trying to override the template with the old location will trigger a deprecation warning that will instruct the user to rename its override file.