Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions spec/draft/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Contents

migration_guide
tutorial_basic
tutorial_api

.. toctree::
:caption: Other
Expand Down
136 changes: 136 additions & 0 deletions spec/draft/tutorial_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
(tutorial-api)=

# Array API Tutorial - Migrating API

The purpose of this tutorial is to show common patterns for migrating your APIs to
the standard-compatible version in the least disruptive manner for the users.
The patterns discussed in the document cover renaming functions and changing their
signatures, with deprecation periods.

## Renaming a function

First common migration that might occur is the need to rename a function to
the one that is present (or is semantically close enough) in the array API standard.

Let's assume our API has a `transpose` function - the one that is not in the standard
under this name. Instead, `permute_dims` is present for permuting the axes of an
array, so we can assume this is the one we want to migrate users to. The original
function is as follows:

```py
def transpose(a, axes=None):
...
```

The first stage is to implement `permute_dims` and deprecate the old one with an
informative migration guide on what should be used instead:

```py
def permute_dims(x, /, axes):
...

def transpose(a, axes=None):
warnings.warn("`transpose` function is deprecated, use `permute_dims` instead.", DeprecationWarning)
...
```

After a deprecation cycle, when you are ready to remove the deprecated function,
you should still leave hints for users in case they skipped deprecated versions.
One option is to track deprecated & removed functions' names in `__getattr__` and
still inform users what has happened and what to do about it:

```py
# in your `__init__.py`

def __getattr__(attr):
...
if attr == "transpose":
raise AttributeError(
f"`transpose` was removed in the ... release. use `permute_dims` instead."
)
```

## Changing a function's signature

Another common pattern during migration to the array API standard is to modify
the signature of a function. The most troublesome parameters are keyword arguments
as it requires users to use the new name.

For this scenario we are about to change `reshape` signature to the one in
the standard:

```py
def reshape(a, newshape):
...
```

We need to rename `newshape` parameter to `shape`, add `copy` parameter, and enforce
new positional/keyword calling format.

After researching how users call our `reshape`, we decided to: make `a` positional
only without an extra deprecation message apart from changelog entry, make `shape`
positional or keyword parameter, and make `newshape` and `copy` keyword only:

```py
def reshape(a, /, shape=None, *, newshape=None, copy=None):
...
```

This way users calling `reshape(arr, (ax1, ax2, ax3))` will not notice any change
in the behavior of the function. Now we need to iron out other scenarios.
Users calling the function with a `newshape=...` need to receive a warning with
a proper recommendation, and the extreme case of both `shape` and `newshape` passed
needs to result in an exception:

```py
import warnings

def reshape(a, /, shape=None, *, newshape=None, copy=None):
if newshape is not None:
warnings.warn(
"`newshape` keyword argument is deprecated, use `shape=...` or pass shape positionally instead.", DeprecationWarning,
)
if shape is not None:
raise TypeError("You cannot specify `newshape` and `shape` arguments at the same time.")
shape = newshape
# proceed with `shape` argument
...
```

Once a deprecation period has passed, we replace the deprecation warning with
a to a `TypeError`, with the same migration message as before:

```py
def reshape(a, /, shape=None, *, newshape=None, copy=None):
if newshape is not None:
raise TypeError(
"`newshape` keyword argument is not supported anymore, use `shape=...` "
"or pass shape positionally instead."
)
...
```

In case your original parameter supported `None` as an actual shape as well, you
should consider introducing a custom `_NoValue` instance for tracking whether
the parameter was set or not:

```py
class _NoValueType:
__instance = None

def __new__(cls):
if not cls.__instance:
cls.__instance = super().__new__(cls)
return cls.__instance

_NoValue = _NoValueType()
# Use `newshape=_NoValue` in the signature
```

The final step is to remove deprecated and already unsupported parameters, which
leaves us with a final and standard compatible version for the next release:

```py
def reshape(a, /, shape, *, copy=None):
...
```
6 changes: 3 additions & 3 deletions spec/draft/tutorial_basic.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
(tutorial-basic)=

# Array API Tutorial
# Array API Tutorial - Essentials

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

The example presented here comes from the [`graphblas-algorithms`](https://github.com/python-graphblas/graphblas-algorithms).
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
Expand Down
Loading