Skip to content
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

Document the deprecation policy and add the deprecate_parameter decorator to deprecate parameters #1160

Merged
merged 28 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
35466a2
Add the deprecate_parameter decorator to deprecate parameters
seisman Mar 27, 2021
0d12dfc
Apply suggestions from code review
seisman Apr 4, 2021
93f721b
Improve the docstrings and doctests
seisman Apr 6, 2021
98ea6eb
Improve the doctest to catch the warnings
seisman Apr 6, 2021
5104bfc
Merge branch 'master' into deprecate-decorator
seisman Apr 6, 2021
b4a79b7
[format-command] fixes
actions-bot Apr 6, 2021
e8eda6c
Use FutureWarning instead of DeprecationWarning
seisman Apr 6, 2021
d491846
Add extra notes for the decorator
seisman Apr 6, 2021
59450ff
Revert the changes in plot.py
seisman Apr 6, 2021
59bacf7
Bump conda-incubator/setup-miniconda from v2.1.0 to v2.1.1 (#1187)
dependabot[bot] Apr 6, 2021
0932231
Bump codecov/codecov-action from v1.3.1 to v1.3.2 (#1188)
dependabot[bot] Apr 6, 2021
65146bd
Merge branch 'master' into deprecate-decorator
seisman Apr 6, 2021
a3f63c7
format the docstrings
seisman Apr 6, 2021
fb03a8e
Merge branch 'master' into deprecate-decorator
seisman Apr 9, 2021
7e76820
Merge branch 'master' into deprecate-decorator
seisman Apr 10, 2021
0d46199
Document the deprecation policy
seisman Apr 10, 2021
3217fc5
Deprecation should be documented in the release notes [skip ci]
seisman Apr 10, 2021
b8d32b6
Typo [skip ci]
seisman Apr 10, 2021
0584882
Typo [skip ci]
seisman Apr 10, 2021
341e90c
Apply suggestions from code review
seisman Apr 12, 2021
3e04397
Merge branch 'master' into deprecate-decorator
seisman Apr 13, 2021
5d993cc
Fix 'sizes' to 'size' in the example
seisman Apr 13, 2021
1055725
Apply suggestions from code review
seisman Apr 13, 2021
82dfb90
Apply suggestions from code review
seisman Apr 15, 2021
c73b80b
Add a test for co-existence of old and new parameter names
seisman Apr 16, 2021
5018133
Merge branch 'master' into deprecate-decorator
seisman Apr 16, 2021
15f3b71
Format
seisman Apr 16, 2021
3280bf5
Merge branch 'master' into deprecate-decorator
seisman Apr 19, 2021
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
41 changes: 41 additions & 0 deletions doc/maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,47 @@ supported version of Python. Minimum Python and NumPy version support should be
adjusted upward on every major and minor release, but never on a patch release.


## Backwards compatibility and deprecation policy

PyGMT is still undergoing rapid developement. All of the API is subject to change
until the v1.0.0 release.

Basic policy for backwards compatibility:

- Any incompatible changes should go through the deprecation process below.
- Incompatible changes are only allowed in major and minor releases, not in
patch releases.
- Incompatible changes should be documented in the release notes.

When making incompatible changes, we should follow the process:

- Discuss whether the incompatible changes are necessary on GitHub.
- Make the changes in a backwards compatible way, and raise a `FutureWarning`
warning for old usage. At least one test using the old usage should be added.
- The warning message should clearly explain the changes and include the versions
in which the old usage is deprecated and is expected to be removed.
- The `FutureWarning` warning should appear for 2-4 minor versions, depending on
the impact of the changes. It means the deprecation period usually lasts
3-12 months.
- Remove the old usage and warning when reaching the declared version.

To rename a function parameter, add the `@deprecated_parameter` decorator
before the function definition (but after the `@use_alias` decorator if it exists).
Here is an example:

```
@fmt_docstring
@use_alias(J="projection", R="region", V="verbose")
@kwargs_to_strings(R="sequence")
@deprecate_parameter("sizes", "size", "v0.4.0", remove_version="v0.6.0")
def plot(self, x=None, y=None, data=None, size=None, direction=None, **kwargs):
pass
```

In this case, the old parameter name `sizes` is deprecated since v0.4.0, and will be
fully removed in v0.6.0. The new parameter name is `size`.


## Making a Release

We try to automate the release process as much as possible.
Expand Down
7 changes: 6 additions & 1 deletion pygmt/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""
Functions, classes, decorators, and context managers to help wrap GMT modules.
"""
from pygmt.helpers.decorators import fmt_docstring, kwargs_to_strings, use_alias
from pygmt.helpers.decorators import (
deprecate_parameter,
fmt_docstring,
kwargs_to_strings,
use_alias,
)
from pygmt.helpers.tempfile import GMTTempFile, unique_name
from pygmt.helpers.utils import (
args_in_kwargs,
Expand Down
79 changes: 79 additions & 0 deletions pygmt/helpers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""
import functools
import textwrap
import warnings

import numpy as np
from pygmt.exceptions import GMTInvalidInput
Expand Down Expand Up @@ -439,3 +440,81 @@ def new_module(*args, **kwargs):
return new_module

return converter


def deprecate_parameter(oldname, newname, deprecate_version, remove_version):
"""
Decorator to deprecate a parameter.
seisman marked this conversation as resolved.
Show resolved Hide resolved

The old parameter name will be automatically swapped to the new parameter
name, and users will receive a FutureWarning to inform them of the pending
deprecation.

Use this decorator below the ``use_alias`` decorator.

Parameters
----------
oldname : str
The old, deprecated parameter name.
newname : str
The new parameter name.
deprecate_version : str
The PyGMT version when the old parameter starts to be deprecated.
remove_version : str
The PyGMT version when the old parameter will be fully removed.

Examples
--------
>>> @deprecate_parameter("sizes", "size", "v0.0.0", "v9.9.9")
... @deprecate_parameter("colors", "color", "v0.0.0", "v9.9.9")
... @deprecate_parameter("infile", "data", "v0.0.0", "v9.9.9")
... def module(data, size=0, **kwargs):
... "A module that prints the arguments it received"
... print(f"data={data}, size={size}, color={kwargs['color']}")
>>> # new names are supported
>>> module(data="table.txt", size=5.0, color="red")
data=table.txt, size=5.0, color=red
>>> # old names are supported, FutureWarning warnings are reported
>>> with warnings.catch_warnings(record=True) as w:
... module(infile="table.txt", sizes=5.0, colors="red")
... # check the number of warnings
... assert len(w) == 3
... for i in range(len(w)):
... assert issubclass(w[i].category, FutureWarning)
... assert "deprecated" in str(w[i].message)
...
data=table.txt, size=5.0, color=red
>>> # using both old and new names will raise an GMTInvalidInput exception
>>> import pytest
>>> with pytest.raises(GMTInvalidInput):
... module(data="table.txt", size=5.0, sizes=4.0)
"""

def deprecator(module_func):
"""
The decorator that creates the new function to work with both old and
new parameters.
"""

@functools.wraps(module_func)
def new_module(*args, **kwargs):
"""
New module instance that converts old parameters to new parameters.
"""
if oldname in kwargs:
if newname in kwargs:
raise GMTInvalidInput(
core-man marked this conversation as resolved.
Show resolved Hide resolved
f"Can't provide both '{newname}' and '{oldname}'."
)
msg = (
f"The '{oldname}' parameter has been deprecated since {deprecate_version}"
f" and will be removed in {remove_version}."
f" Please use '{newname}' instead."
)
warnings.warn(msg, category=FutureWarning, stacklevel=2)
kwargs[newname] = kwargs.pop(oldname)
return module_func(*args, **kwargs)

return new_module

return deprecator