Skip to content

Commit 3469dfe

Browse files
seismanweiji14Meghan Jonescore-man
authored
Document the deprecation policy and addd the deprecate_parameter decorator to deprecate parameters (#1160)
* Add the deprecate_parameter decorator to deprecate parameters * Use FutureWarning instead of DeprecationWarning * Document the deprecation policy Co-authored-by: Wei Ji <[email protected]> Co-authored-by: Meghan Jones <[email protected]> Co-authored-by: Yao Jiayuan <[email protected]>
1 parent 8274dc5 commit 3469dfe

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

doc/maintenance.md

+41
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,47 @@ supported version of Python. Minimum Python and NumPy version support should be
146146
adjusted upward on every major and minor release, but never on a patch release.
147147

148148

149+
## Backwards compatibility and deprecation policy
150+
151+
PyGMT is still undergoing rapid developement. All of the API is subject to change
152+
until the v1.0.0 release.
153+
154+
Basic policy for backwards compatibility:
155+
156+
- Any incompatible changes should go through the deprecation process below.
157+
- Incompatible changes are only allowed in major and minor releases, not in
158+
patch releases.
159+
- Incompatible changes should be documented in the release notes.
160+
161+
When making incompatible changes, we should follow the process:
162+
163+
- Discuss whether the incompatible changes are necessary on GitHub.
164+
- Make the changes in a backwards compatible way, and raise a `FutureWarning`
165+
warning for old usage. At least one test using the old usage should be added.
166+
- The warning message should clearly explain the changes and include the versions
167+
in which the old usage is deprecated and is expected to be removed.
168+
- The `FutureWarning` warning should appear for 2-4 minor versions, depending on
169+
the impact of the changes. It means the deprecation period usually lasts
170+
3-12 months.
171+
- Remove the old usage and warning when reaching the declared version.
172+
173+
To rename a function parameter, add the `@deprecated_parameter` decorator
174+
before the function definition (but after the `@use_alias` decorator if it exists).
175+
Here is an example:
176+
177+
```
178+
@fmt_docstring
179+
@use_alias(J="projection", R="region", V="verbose")
180+
@kwargs_to_strings(R="sequence")
181+
@deprecate_parameter("sizes", "size", "v0.4.0", remove_version="v0.6.0")
182+
def plot(self, x=None, y=None, data=None, size=None, direction=None, **kwargs):
183+
pass
184+
```
185+
186+
In this case, the old parameter name `sizes` is deprecated since v0.4.0, and will be
187+
fully removed in v0.6.0. The new parameter name is `size`.
188+
189+
149190
## Making a Release
150191

151192
We try to automate the release process as much as possible.

pygmt/helpers/__init__.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
"""
22
Functions, classes, decorators, and context managers to help wrap GMT modules.
33
"""
4-
from pygmt.helpers.decorators import fmt_docstring, kwargs_to_strings, use_alias
4+
from pygmt.helpers.decorators import (
5+
deprecate_parameter,
6+
fmt_docstring,
7+
kwargs_to_strings,
8+
use_alias,
9+
)
510
from pygmt.helpers.tempfile import GMTTempFile, unique_name
611
from pygmt.helpers.utils import (
712
args_in_kwargs,

pygmt/helpers/decorators.py

+80
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
import functools
99
import textwrap
10+
import warnings
1011

1112
import numpy as np
1213
from pygmt.exceptions import GMTInvalidInput
@@ -439,3 +440,82 @@ def new_module(*args, **kwargs):
439440
return new_module
440441

441442
return converter
443+
444+
445+
def deprecate_parameter(oldname, newname, deprecate_version, remove_version):
446+
"""
447+
Decorator to deprecate a parameter.
448+
449+
The old parameter name will be automatically swapped to the new parameter
450+
name, and users will receive a FutureWarning to inform them of the pending
451+
deprecation.
452+
453+
Use this decorator below the ``use_alias`` decorator.
454+
455+
Parameters
456+
----------
457+
oldname : str
458+
The old, deprecated parameter name.
459+
newname : str
460+
The new parameter name.
461+
deprecate_version : str
462+
The PyGMT version when the old parameter starts to be deprecated.
463+
remove_version : str
464+
The PyGMT version when the old parameter will be fully removed.
465+
466+
Examples
467+
--------
468+
>>> @deprecate_parameter("sizes", "size", "v0.0.0", "v9.9.9")
469+
... @deprecate_parameter("colors", "color", "v0.0.0", "v9.9.9")
470+
... @deprecate_parameter("infile", "data", "v0.0.0", "v9.9.9")
471+
... def module(data, size=0, **kwargs):
472+
... "A module that prints the arguments it received"
473+
... print(f"data={data}, size={size}, color={kwargs['color']}")
474+
>>> # new names are supported
475+
>>> module(data="table.txt", size=5.0, color="red")
476+
data=table.txt, size=5.0, color=red
477+
>>> # old names are supported, FutureWarning warnings are reported
478+
>>> with warnings.catch_warnings(record=True) as w:
479+
... module(infile="table.txt", sizes=5.0, colors="red")
480+
... # check the number of warnings
481+
... assert len(w) == 3
482+
... for i in range(len(w)):
483+
... assert issubclass(w[i].category, FutureWarning)
484+
... assert "deprecated" in str(w[i].message)
485+
...
486+
data=table.txt, size=5.0, color=red
487+
>>> # using both old and new names will raise an GMTInvalidInput exception
488+
>>> import pytest
489+
>>> with pytest.raises(GMTInvalidInput):
490+
... module(data="table.txt", size=5.0, sizes=4.0)
491+
...
492+
"""
493+
494+
def deprecator(module_func):
495+
"""
496+
The decorator that creates the new function to work with both old and
497+
new parameters.
498+
"""
499+
500+
@functools.wraps(module_func)
501+
def new_module(*args, **kwargs):
502+
"""
503+
New module instance that converts old parameters to new parameters.
504+
"""
505+
if oldname in kwargs:
506+
if newname in kwargs:
507+
raise GMTInvalidInput(
508+
f"Can't provide both '{newname}' and '{oldname}'."
509+
)
510+
msg = (
511+
f"The '{oldname}' parameter has been deprecated since {deprecate_version}"
512+
f" and will be removed in {remove_version}."
513+
f" Please use '{newname}' instead."
514+
)
515+
warnings.warn(msg, category=FutureWarning, stacklevel=2)
516+
kwargs[newname] = kwargs.pop(oldname)
517+
return module_func(*args, **kwargs)
518+
519+
return new_module
520+
521+
return deprecator

0 commit comments

Comments
 (0)