From 08bd1f0d27d8c061d323b77b77061fb6adc13f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Tue, 7 Apr 2026 13:09:50 +0200 Subject: [PATCH 1/7] Add tutorial about API migration --- spec/draft/index.rst | 1 + spec/draft/tutorial_api.md | 136 +++++++++++++++++++++++++++++++++++ spec/draft/tutorial_basic.md | 6 +- 3 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 spec/draft/tutorial_api.md diff --git a/spec/draft/index.rst b/spec/draft/index.rst index 5df9b31cb..37db3b773 100644 --- a/spec/draft/index.rst +++ b/spec/draft/index.rst @@ -36,6 +36,7 @@ Contents migration_guide tutorial_basic + tutorial_api .. toctree:: :caption: Other diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md new file mode 100644 index 000000000..1b8b38091 --- /dev/null +++ b/spec/draft/tutorial_api.md @@ -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): + ... +``` diff --git a/spec/draft/tutorial_basic.md b/spec/draft/tutorial_basic.md index ea8a3b453..4e0397d2e 100644 --- a/spec/draft/tutorial_basic.md +++ b/spec/draft/tutorial_basic.md @@ -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 From 16d770876bbd334e7b1faac8623c0fc68cd0f2f6 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:51:50 -0700 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index 1b8b38091..9698e6166 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -3,18 +3,16 @@ # 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 standard-compatible version in the least disruptive manner for users. The patterns discussed in the document cover renaming functions and changing their -signatures, with deprecation periods. +signatures, along with deprecation periods. ## Renaming a function -First common migration that might occur is the need to rename a function to +The first common migration 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 +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 and is the function to which we want users to migrate. The original function is as follows: ```py @@ -34,9 +32,9 @@ def transpose(a, axes=None): ... ``` -After a deprecation cycle, when you are ready to remove the deprecated function, +After a deprecation cycle and 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 +One option is to track the names of deprecated and removed functions in `__getattr__` and still inform users what has happened and what to do about it: ```py @@ -56,7 +54,7 @@ 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 +For this scenario, suppose we want to update signature of `reshape` to match the one in the standard: ```py @@ -64,10 +62,10 @@ def reshape(a, newshape): ... ``` -We need to rename `newshape` parameter to `shape`, add `copy` parameter, and enforce +We need to rename `newshape` parameter to `shape`, add a `copy` parameter, and enforce new positional/keyword calling format. -After researching how users call our `reshape`, we decided to: make `a` positional +After researching how users call our `reshape`, we decided to do the following: 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: @@ -77,7 +75,7 @@ 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. +in the behavior of the function. Next, 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: @@ -98,7 +96,7 @@ def reshape(a, /, shape=None, *, newshape=None, copy=None): ``` Once a deprecation period has passed, we replace the deprecation warning with -a to a `TypeError`, with the same migration message as before: +a `TypeError` and use the same migration message as before: ```py def reshape(a, /, shape=None, *, newshape=None, copy=None): From f2714a1def6a29e32777152a8d6884ea6ba1a757 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:52:40 -0700 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index 9698e6166..711fdfe37 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -3,7 +3,7 @@ # 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 users. +a standard-compatible version in the least disruptive manner for users. The patterns discussed in the document cover renaming functions and changing their signatures, along with deprecation periods. From 130b5c720f3511d35377e56c4307f5cb0b26d4f2 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:52:54 -0700 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index 711fdfe37..e980a7146 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -1,6 +1,6 @@ (tutorial-api)= -# Array API Tutorial - Migrating API +# Array API Tutorial - Migrating APIs The purpose of this tutorial is to show common patterns for migrating your APIs to a standard-compatible version in the least disruptive manner for users. From 0e132490ac44530e0b2cf987c7d4f839d83a9af0 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:53:38 -0700 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index e980a7146..5ddba6b24 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -12,7 +12,7 @@ signatures, along with deprecation periods. The first common migration 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 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 and is the function to which we want users to migrate. The original +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 function is as follows: ```py From a7fc95dbe141a44b94811eb085f42e0b7f6cce75 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:55:02 -0700 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index 5ddba6b24..74578a33f 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -66,8 +66,8 @@ We need to rename `newshape` parameter to `shape`, add a `copy` parameter, and e new positional/keyword calling format. After researching how users call our `reshape`, we decided to do the following: 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: +only without an extra deprecation message apart from a changelog entry, make `shape` +a positional or keyword parameter, and make `newshape` and `copy` keyword only: ```py def reshape(a, /, shape=None, *, newshape=None, copy=None): From c84b313460c0313af434e3fc6db120e3da86aa99 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 23 Apr 2026 09:55:22 -0700 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/draft/tutorial_api.md b/spec/draft/tutorial_api.md index 74578a33f..38380ebfd 100644 --- a/spec/draft/tutorial_api.md +++ b/spec/draft/tutorial_api.md @@ -54,7 +54,7 @@ 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, suppose we want to update signature of `reshape` to match the one in +For this scenario, suppose we want to update the signature of `reshape` to match the one in the standard: ```py