Skip to content

Commit 408141c

Browse files
mtsokolkgryte
andauthored
docs: add tutorial about API migration
PR-URL: #1002 Co-authored-by: Athan Reines <kgryte@gmail.com> Reviewed-by: Athan Reines <kgryte@gmail.com>
1 parent df0cfe0 commit 408141c

3 files changed

Lines changed: 138 additions & 3 deletions

File tree

spec/draft/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Contents
3636

3737
migration_guide
3838
tutorial_basic
39+
tutorial_api
3940

4041
.. toctree::
4142
:caption: Other

spec/draft/tutorial_api.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
(tutorial-api)=
2+
3+
# Array API Tutorial - Migrating APIs
4+
5+
The purpose of this tutorial is to show common patterns for migrating your APIs to
6+
a standard-compatible version in the least disruptive manner for users.
7+
The patterns discussed in the document cover renaming functions and changing their
8+
signatures, along with deprecation periods.
9+
10+
## Renaming a function
11+
12+
The first common migration is the need to rename a function to
13+
the one that is present (or is semantically close enough) in the array API standard.
14+
15+
Let's assume our API has a `transpose` function for permuting the axes of an array, but which has no matching name in the standard. Instead, the standard has a `permute_dims` API which performs the equivalent operation. The original
16+
function is as follows:
17+
18+
```py
19+
def transpose(a, axes=None):
20+
...
21+
```
22+
23+
The first stage is to implement `permute_dims` and deprecate the old one with an
24+
informative migration guide on what should be used instead:
25+
26+
```py
27+
def permute_dims(x, /, axes):
28+
...
29+
30+
def transpose(a, axes=None):
31+
warnings.warn("`transpose` function is deprecated, use `permute_dims` instead.", DeprecationWarning)
32+
...
33+
```
34+
35+
After a deprecation cycle and when you are ready to remove the deprecated function,
36+
you should still leave hints for users in case they skipped deprecated versions.
37+
One option is to track the names of deprecated and removed functions in `__getattr__` and
38+
still inform users what has happened and what to do about it:
39+
40+
```py
41+
# in your `__init__.py`
42+
43+
def __getattr__(attr):
44+
...
45+
if attr == "transpose":
46+
raise AttributeError(
47+
f"`transpose` was removed in the ... release. use `permute_dims` instead."
48+
)
49+
```
50+
51+
## Changing a function's signature
52+
53+
Another common pattern during migration to the array API standard is to modify
54+
the signature of a function. The most troublesome parameters are keyword arguments
55+
as it requires users to use the new name.
56+
57+
For this scenario, suppose we want to update the signature of `reshape` to match the one in
58+
the standard:
59+
60+
```py
61+
def reshape(a, newshape):
62+
...
63+
```
64+
65+
We need to rename `newshape` parameter to `shape`, add a `copy` parameter, and enforce
66+
new positional/keyword calling format.
67+
68+
After researching how users call our `reshape`, we decided to do the following: make `a` positional
69+
only without an extra deprecation message apart from a changelog entry, make `shape`
70+
a positional or keyword parameter, and make `newshape` and `copy` keyword only:
71+
72+
```py
73+
def reshape(a, /, shape=None, *, newshape=None, copy=None):
74+
...
75+
```
76+
77+
This way users calling `reshape(arr, (ax1, ax2, ax3))` will not notice any change
78+
in the behavior of the function. Next, we need to iron out other scenarios.
79+
Users calling the function with a `newshape=...` need to receive a warning with
80+
a proper recommendation, and the extreme case of both `shape` and `newshape` passed
81+
needs to result in an exception:
82+
83+
```py
84+
import warnings
85+
86+
def reshape(a, /, shape=None, *, newshape=None, copy=None):
87+
if newshape is not None:
88+
warnings.warn(
89+
"`newshape` keyword argument is deprecated, use `shape=...` or pass shape positionally instead.", DeprecationWarning,
90+
)
91+
if shape is not None:
92+
raise TypeError("You cannot specify `newshape` and `shape` arguments at the same time.")
93+
shape = newshape
94+
# proceed with `shape` argument
95+
...
96+
```
97+
98+
Once a deprecation period has passed, we replace the deprecation warning with
99+
a `TypeError` and use the same migration message as before:
100+
101+
```py
102+
def reshape(a, /, shape=None, *, newshape=None, copy=None):
103+
if newshape is not None:
104+
raise TypeError(
105+
"`newshape` keyword argument is not supported anymore, use `shape=...` "
106+
"or pass shape positionally instead."
107+
)
108+
...
109+
```
110+
111+
In case your original parameter supported `None` as an actual shape as well, you
112+
should consider introducing a custom `_NoValue` instance for tracking whether
113+
the parameter was set or not:
114+
115+
```py
116+
class _NoValueType:
117+
__instance = None
118+
119+
def __new__(cls):
120+
if not cls.__instance:
121+
cls.__instance = super().__new__(cls)
122+
return cls.__instance
123+
124+
_NoValue = _NoValueType()
125+
# Use `newshape=_NoValue` in the signature
126+
```
127+
128+
The final step is to remove deprecated and already unsupported parameters, which
129+
leaves us with a final and standard compatible version for the next release:
130+
131+
```py
132+
def reshape(a, /, shape, *, copy=None):
133+
...
134+
```

spec/draft/tutorial_basic.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
(tutorial-basic)=
22

3-
# Array API Tutorial
3+
# Array API Tutorial - Essentials
44

5-
In this tutorial, we're going to demonstrate how to migrate to the Array API from the array consumer's
6-
point of view for a simple graph algorithm.
5+
In this tutorial, we're going to demonstrate how to migrate to the Array API
6+
from the array consumer's point of view for a simple graph algorithm.
77

88
The example presented here comes from the [`graphblas-algorithms`](https://github.com/python-graphblas/graphblas-algorithms).
99
library. In particular, we'll be migrating [the HITS algorithm](https://github.com/python-graphblas/graphblas-algorithms/blob/35dbc90e808c6bf51b63d51d8a63f59238c02975/graphblas_algorithms/algorithms/link_analysis/hits_alg.py#L9), which is

0 commit comments

Comments
 (0)