Skip to content

add deprecation chapter #1850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: 6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
973020c
add deprecation chapter
MrTango Feb 11, 2025
bd6eac9
fix broken links in deprecation chapter
MrTango Feb 11, 2025
ff7461b
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
6027930
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
e6acc83
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
b909069
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
b0923fb
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
7ca4ede
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
66a58dc
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
c859367
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
4e32566
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
c7cad7a
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
a9fc020
remove meta.zcml attribute from z3c.jbot include
MrTango Feb 11, 2025
151aa67
Update docs/backend/deprecation.md
MrTango Feb 11, 2025
cff3ad6
Fix ty
ale-rt Feb 11, 2025
d0286e4
Fix typo
ale-rt Feb 11, 2025
1c733a3
Fix typo
ale-rt Feb 11, 2025
49e96f8
Update docs/backend/deprecation.md
MrTango Feb 12, 2025
8608dff
Update docs/backend/deprecation.md
MrTango Feb 12, 2025
155c344
Update docs/backend/deprecation.md
MrTango Feb 12, 2025
295f3f5
Update docs/backend/deprecation.md
MrTango Feb 12, 2025
5db3027
Update docs/backend/deprecation.md
MrTango Feb 12, 2025
cd5e77e
Merge branch '6.0' into chapter-deprecation
MrTango Feb 12, 2025
0bacab8
Apply suggestions from code review
stevepiercy Feb 16, 2025
245f3b8
Merge branch '6.0' into chapter-deprecation
stevepiercy Feb 16, 2025
32a29d7
Merge branch '6.0' into chapter-deprecation
stevepiercy Feb 17, 2025
dab9e49
Merge branch '6.0' into chapter-deprecation
jensens Feb 17, 2025
1803a60
Merge branch '6.0' into chapter-deprecation
jensens Feb 18, 2025
ce2454c
Merge branch '6.0' into chapter-deprecation
ale-rt Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 318 additions & 0 deletions docs/backend/deprecation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
---
myst:
html_meta:
"description": "A guide how to do deprecations, including Python, ZCML and templates in Plone."
"property=og:description": "A guide how to do deprecations, including Python, ZCML and templates in Plone."
"property=og:title": "Deprecation"
"keywords": "deprecation, zcml, template, jbot"
---

(backend-deprecation-label)=
# Deprecation

## Introduction

This document describes rationales, configuration and best practices of deprecations in Plone, Zope and Python.
It is meant as a styleguide on how to apply deprecations in Plone core packages.
It also has a value as a general overview on how to deprecate in Python.


### Why Deprecation

At some point we:

- need to get rid of old code,
- want to unify API style (consistent API),
- fix typos in namings,
- move code or templates around (inside package or to another package).

While refactoring code, moving modules, functions, classes and methods is often needed.
To not break third party code imports from the old place or usage of old functions/ methods must work for while.
Deprecated methods are usually removed with the next major release of Plone.

Following the [semantic versioning guideline](https://semver.org) is recommended.

### Help Programmers, No annoyance

The developers should use code deprecations to support the consumers of the code.
From their point of view, Plone core code is an API to them.
Any change is annoying to them anyway, but they feel better if deprecation warnings are telling them what to do.

Deprecations must always log at level *warning* and have to answers the question:

**"Why is the code gone from the old place? What to do instead?"**

A short message is enough., i.e.:

- "Replaced by new API xyz, found at abc.cde".,
- "Moved to xyz, because of abc.",
- "Name had a typo, new name is "xyz".

All logging has to be done once, i.e. on first usage or first import.
It must not flood the logs.

### Use Cases

Renaming

: We may want to rename classes, methods, functions or global or class variables in order to get a more consistent API or because of a typo, etc.
We never just rename, we always provide a deprecated version logging a verbose deprecation warning with information where to
import from in future.

Moving a module, class, function, etc to another place

: For some reason, i.e. merging packages, consistent API or resolving cirular import problems, we need to move code around.
When imported from the old place it logs a verbose deprecation warning with information where to import from in future.

Deprecation of a whole package

: A whole [package](https://docs.python.org/3/tutorial/modules.html#packages)

- all imports still working, logging deprecation warnings on first import
- ZCML still exists, but is empty (or includes the zcml from the new place if theres no auto import (i.e. for meta.zcml).

Deprecation of a whole released/ installable package.

: We will provide a last major release with no 'real' code, only backward compatible (bbb) imports of public API are provided.
This will be done the way described above for a whole package.
The README clearly states why it was moved and where to find the code now.

Deprecation of a GenericSetup profile

: They may got renamed for consistency or are superfluos after an update.
Code does not need to break to support this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything up to this point is a Conceptual Guide, and needs to be split into a separate file placed in the conceptual-guides directory.

Everything after this point is a How-to Guide and should be moved under Developer guide. This section is a recent addition, and more suitable for this content.

## Enable Deprecation Warnings

### Zope

Zope does configure logging and warnings, so the steps below (under section Python) are not needed.

Using `plone.recipe.zope2instance` add the option `deprecation-warnings = on` to the buildouts `[instance]` section.

```ini
[buildout]
parts = instance

[instance]
recipe = plone.recipe.zope2instance
...
deprecation-warnings = on
...
```

This adds this line to the `zope.conf` file:

```
debug-mode on
```

Without the recipe this can be set manually as well:
In `zope.conf` custom filters for warnings can be defined.

```xml
...
<warnfilter>
action always
category exceptions.DeprecationWarning
</warnfilter>
...
```

### Python

Enable Warnings

: Warnings are written to `stderr` by default, but `DeprecationWarning` output is surpressed by default.

Output can be enabled by starting the Python interpreter with the [-W \[all|module|once\]](https://docs.python.org/3/using/cmdline.html#cmdoption-W) option.

It is possible to enable output in code too:

```python
import warnings
warnings.simplefilter("module")
```

Configure Logging

: Once output is enabled it is possible to [redirect warnings to the logger](https://docs.python.org/3/library/logging.html#logging.captureWarnings):

```python
import logging
logging.captureWarnings(True)
```

### Running tests

In Plone tests deprecation warnings are not shown by default.
The `zope.conf` setting is not taken into account.

In order to enable deprecation warnings,
the Python way with the `-W` command option must to be used.

Given youre using a modern buildout with virtualenv as recommended,
the call looks like so:

```bash
./bin/python -W module ./bin/test
```

## Deprecation Best Practice

### Vanilla Deprecation Messages

Python offers a built-in `DeprecationWarning` which can be issued using standard libraries `warnings` module.

For details read the [official documentation about warnings](https://docs.python.org/3/library/warnings.html).

In short it works like so

```python
import warnings
warnings.warn('deprecated', DeprecationWarning)
```

### Moving Whole Modules

Given a package `old.pkg` with a module `foo.py` need to be moved to a package `new.pkg` as `bar.py`.

[zope.deprecation Moving modules](https://zopedeprecation.readthedocs.io/en/latest/api.html#moving-modules) offers a helper.

1. Move the `foo.py` as `bar.py` to the `new.pkg`.
2. At the old place create a new `foo.py` and add to it

```python
from zope.deprecation import moved
moved('new.pkg.bar', 'Version 2.0')
```

Now you can still import the namespace from `bar` 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

### Moving Whole Packages

This is the same as moving a module, just create for each module a file.

### Deprecating methods and properties

You can use the `@deprecate` decorator from [zope.deprecation Deprecating methods and properties](https://zopedeprecation.readthedocs.io/en/latest/api.html#deprecating-methods-and-properties) to deprecate methods in a module:

```python
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 is for deprecating properties:

```python
from zope.deprecation import deprecated

foo = None
foo = deprecated(foo, 'foo is no more, use bar instead')
```

### Moving functions and classes

Given we have a Python file at `old/foo/bar.py` and want to move some classes or functions to `new/baz/baaz.py`.

Here `zope.deferredimport` offers a deprecation helper.
It also avoids circular imports on initialization time.

```python
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
```

### Deprecating a GenericSetup profile

Starting with GenericSetup 1.8.2 (part of Plone > 5.0.2) the `post_handler` attribute in ZCML can be used to call a function after the profile was applied.
We use this feature to issue a warning.

First we register the same profile twice. Under the new name and under the old name:

```xml
<genericsetup:registerProfile
name="default"
title="My Fance Package"
directory="profiles/default"
description="..."
provides="Products.GenericSetup.interfaces.EXTENSION"
/>

<genericsetup:registerProfile
name="some_confusing_name"
title="My Fance Package (deprecated)"
directory="profiles/some_confusing_name"
description="... (use profile default instaed)"
provides="Products.GenericSetup.interfaces.EXTENSION"
post_handler=".setuphandlers.deprecate_profile_some_confusing_name"
/>
```

And in `setuphandlers.py` add a function:

```python
import warnings

def deprecate_profile_some_confusing_name(tool):
warnings.warn(
'The profile with id "some_confusing_name" was renamed to "default".',
DeprecationWarning
)
```

### Deprecating a template position

Sometimes we need to move templates to new locations. Since addons often use [z3c.jbot](https://github.com/zopefoundation/z3c.jbot) to override templates by their position, we need to point them to the new position as well as make sure that the override still works with the old position.


To deprecate a package:

1. In the old package folders `__init__.py` add a dictionary `jbot_deprecations` that maps the old template locations to their new counterparts, e.g.:

```python
jbot_deprecations = {
"plone.locking.browser.info.pt": "plone.app.layout.viewlets.info.pt"
}
```

2. Add this deprecation snippet to the package `configure.zcml` file:

```{code-block} xml
:emphasize-lines: 6,9-12
:linenos:

<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:zcml="http://namespaces.zope.org/zcml"
>

<include
zcml:condition="installed z3c.jbot"
package="z3c.jbot"
/>
<browser:jbotDeprecated
zcml:condition="have jbot-deprecations"
dictionary=".jbot_deprecations"
/>

</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.
1 change: 1 addition & 0 deletions docs/backend/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ behaviors
configuration-registry
content-types/index
control-panels
deprecation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you all think that this insertion point in the TOC is the most appropiate place for this subject?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not. See #1850 (review).

fields
global-utils
indexing
Expand Down