array-api-strict 2.4__tar.gz → 2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/array-api-tests.yml +15 -9
  2. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/docs-build.yml +3 -3
  3. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/docs-deploy.yml +2 -2
  4. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/publish-package.yml +6 -6
  5. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/ruff.yml +2 -2
  6. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/tests.yml +14 -7
  7. {array_api_strict-2.4 → array_api_strict-2.5}/CHANGELOG.md +91 -2
  8. {array_api_strict-2.4/array_api_strict.egg-info → array_api_strict-2.5}/PKG-INFO +4 -1
  9. {array_api_strict-2.4 → array_api_strict-2.5}/array-api-tests-xfails.txt +16 -0
  10. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/__init__.py +4 -2
  11. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_array_object.py +50 -19
  12. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_creation_functions.py +12 -5
  13. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_data_type_functions.py +16 -4
  14. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_elementwise_functions.py +6 -4
  15. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_flags.py +3 -2
  16. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_info.py +5 -2
  17. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_linalg.py +62 -1
  18. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_manipulation_functions.py +1 -1
  19. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_searching_functions.py +8 -2
  20. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_set_functions.py +23 -1
  21. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_version.py +16 -3
  22. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_array_object.py +93 -27
  23. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_creation_functions.py +2 -0
  24. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_elementwise_functions.py +16 -4
  25. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_flags.py +11 -11
  26. {array_api_strict-2.4 → array_api_strict-2.5/array_api_strict.egg-info}/PKG-INFO +4 -1
  27. array_api_strict-2.5/array_api_strict.egg-info/requires.txt +5 -0
  28. {array_api_strict-2.4 → array_api_strict-2.5}/docs/changelog.md +91 -2
  29. {array_api_strict-2.4 → array_api_strict-2.5}/docs/index.md +12 -11
  30. {array_api_strict-2.4 → array_api_strict-2.5}/pyproject.toml +3 -0
  31. array_api_strict-2.4/array_api_strict.egg-info/requires.txt +0 -1
  32. {array_api_strict-2.4 → array_api_strict-2.5}/.gitattributes +0 -0
  33. {array_api_strict-2.4 → array_api_strict-2.5}/.github/dependabot.yml +0 -0
  34. {array_api_strict-2.4 → array_api_strict-2.5}/.github/workflows/dependabot-auto-merge.yml +0 -0
  35. {array_api_strict-2.4 → array_api_strict-2.5}/.gitignore +0 -0
  36. {array_api_strict-2.4 → array_api_strict-2.5}/LICENSE +0 -0
  37. {array_api_strict-2.4 → array_api_strict-2.5}/README.md +0 -0
  38. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_constants.py +0 -0
  39. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_dtypes.py +0 -0
  40. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_fft.py +0 -0
  41. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_helpers.py +0 -0
  42. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_indexing_functions.py +0 -0
  43. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_linear_algebra_functions.py +0 -0
  44. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_sorting_functions.py +0 -0
  45. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_statistical_functions.py +0 -0
  46. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_typing.py +0 -0
  47. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/_utility_functions.py +0 -0
  48. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/py.typed +0 -0
  49. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/__init__.py +0 -0
  50. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/conftest.py +0 -0
  51. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_data_type_functions.py +0 -0
  52. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_device_support.py +0 -0
  53. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_indexing_functions.py +0 -0
  54. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_linalg.py +0 -0
  55. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_manipulation_functions.py +0 -0
  56. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_searching_functions.py +0 -0
  57. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_set_functions.py +0 -0
  58. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_sorting_functions.py +0 -0
  59. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_statistical_functions.py +0 -0
  60. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict/tests/test_validation.py +0 -0
  61. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict.egg-info/SOURCES.txt +0 -0
  62. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict.egg-info/dependency_links.txt +0 -0
  63. {array_api_strict-2.4 → array_api_strict-2.5}/array_api_strict.egg-info/top_level.txt +0 -0
  64. {array_api_strict-2.4 → array_api_strict-2.5}/docs/Makefile +0 -0
  65. {array_api_strict-2.4 → array_api_strict-2.5}/docs/_static/custom.css +0 -0
  66. {array_api_strict-2.4 → array_api_strict-2.5}/docs/_static/favicon.png +0 -0
  67. {array_api_strict-2.4 → array_api_strict-2.5}/docs/api.rst +0 -0
  68. {array_api_strict-2.4 → array_api_strict-2.5}/docs/conf.py +0 -0
  69. {array_api_strict-2.4 → array_api_strict-2.5}/docs/make.bat +0 -0
  70. {array_api_strict-2.4 → array_api_strict-2.5}/docs/releasing.md +0 -0
  71. {array_api_strict-2.4 → array_api_strict-2.5}/docs/requirements.txt +0 -0
  72. {array_api_strict-2.4 → array_api_strict-2.5}/pytest.ini +0 -0
  73. {array_api_strict-2.4 → array_api_strict-2.5}/requirements-dev.txt +0 -0
  74. {array_api_strict-2.4 → array_api_strict-2.5}/requirements.txt +0 -0
  75. {array_api_strict-2.4 → array_api_strict-2.5}/ruff.toml +0 -0
  76. {array_api_strict-2.4 → array_api_strict-2.5}/setup.cfg +0 -0
@@ -3,33 +3,39 @@ name: Array API Tests
3
3
  on: [push, pull_request]
4
4
 
5
5
  env:
6
- PYTEST_ARGS: "-v -rxXfE --hypothesis-disable-deadline --max-examples 200"
7
- API_VERSIONS: "2023.12 2024.12"
6
+ PYTEST_ARGS: "-v -rxXfE --hypothesis-disable-deadline --max-examples 500 -n 4"
7
+ API_VERSIONS: "2023.12 2024.12 2025.12"
8
8
 
9
9
  jobs:
10
10
  array-api-tests:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- python-version: ['3.12', '3.13']
15
- numpy-version: ['1.26', '2.2', 'dev']
14
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
15
+ numpy-version: ['1.26', '2.3.5', 'dev']
16
16
  exclude:
17
+ - python-version: '3.10'
18
+ numpy-version: '2.3.5'
19
+ - python-version: '3.10'
20
+ numpy-version: 'dev'
17
21
  - python-version: '3.13'
18
22
  numpy-version: '1.26'
19
-
23
+ - python-version: '3.14'
24
+ numpy-version: '1.26'
25
+ fail-fast: false
20
26
  steps:
21
27
  - name: Checkout array-api-strict
22
- uses: actions/checkout@v4
28
+ uses: actions/checkout@v6
23
29
  with:
24
30
  path: array-api-strict
25
31
  - name: Checkout array-api-tests
26
- uses: actions/checkout@v4
32
+ uses: actions/checkout@v6
27
33
  with:
28
34
  repository: data-apis/array-api-tests
29
35
  submodules: 'true'
30
36
  path: array-api-tests
31
37
  - name: Set up Python ${{ matrix.python-version }}
32
- uses: actions/setup-python@v5
38
+ uses: actions/setup-python@v6
33
39
  with:
34
40
  python-version: ${{ matrix.python-version }}
35
41
  - name: Install Dependencies
@@ -42,7 +48,7 @@ jobs:
42
48
  fi
43
49
  python -m pip install ${GITHUB_WORKSPACE}/array-api-strict
44
50
  python -m pip install -r ${GITHUB_WORKSPACE}/array-api-tests/requirements.txt
45
- python -m pip install hypothesis
51
+ python -m pip install hypothesis pytest-xdist
46
52
  - name: Run the array API testsuite
47
53
  env:
48
54
  ARRAY_API_TESTS_MODULE: array_api_strict
@@ -6,8 +6,8 @@ jobs:
6
6
  docs-build:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v4
10
- - uses: actions/setup-python@v5
9
+ - uses: actions/checkout@v6
10
+ - uses: actions/setup-python@v6
11
11
  - name: Install Dependencies
12
12
  run: |
13
13
  python -m pip install -r docs/requirements.txt
@@ -16,7 +16,7 @@ jobs:
16
16
  cd docs
17
17
  make html
18
18
  - name: Upload Artifact
19
- uses: actions/upload-artifact@v4
19
+ uses: actions/upload-artifact@v6
20
20
  with:
21
21
  name: docs-build
22
22
  path: docs/_build/html
@@ -11,9 +11,9 @@ jobs:
11
11
  environment:
12
12
  name: docs-deploy
13
13
  steps:
14
- - uses: actions/checkout@v4
14
+ - uses: actions/checkout@v6
15
15
  - name: Download Artifact
16
- uses: dawidd6/action-download-artifact@v10
16
+ uses: dawidd6/action-download-artifact@v14
17
17
  with:
18
18
  workflow: docs-build.yml
19
19
  name: docs-build
@@ -30,12 +30,12 @@ jobs:
30
30
  runs-on: ubuntu-latest
31
31
 
32
32
  steps:
33
- - uses: actions/checkout@v4
33
+ - uses: actions/checkout@v6
34
34
  with:
35
35
  fetch-depth: 0
36
36
 
37
37
  - name: Set up Python
38
- uses: actions/setup-python@v5
38
+ uses: actions/setup-python@v6
39
39
  with:
40
40
  python-version: '3.x'
41
41
 
@@ -62,7 +62,7 @@ jobs:
62
62
  run: python -m zipfile --list dist/array_api_strict-*.whl
63
63
 
64
64
  - name: Upload distribution artifact
65
- uses: actions/upload-artifact@v4
65
+ uses: actions/upload-artifact@v6
66
66
  with:
67
67
  name: dist-artifact
68
68
  path: dist
@@ -83,7 +83,7 @@ jobs:
83
83
 
84
84
  steps:
85
85
  - name: Download distribution artifact
86
- uses: actions/download-artifact@v4
86
+ uses: actions/download-artifact@v7
87
87
  with:
88
88
  name: dist-artifact
89
89
  path: dist
@@ -97,14 +97,14 @@ jobs:
97
97
  # if: >-
98
98
  # (github.event_name == 'push' && startsWith(github.ref, 'refs/tags'))
99
99
  # || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')
100
- # uses: pypa/gh-action-pypi-publish@v1.12.4
100
+ # uses: pypa/gh-action-pypi-publish@v1.13.0
101
101
  # with:
102
102
  # repository-url: https://test.pypi.org/legacy/
103
103
  # print-hash: true
104
104
 
105
105
  - name: Publish distribution 📦 to PyPI
106
106
  if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
107
- uses: pypa/gh-action-pypi-publish@v1.12.4
107
+ uses: pypa/gh-action-pypi-publish@v1.13.0
108
108
  with:
109
109
  print-hash: true
110
110
 
@@ -5,9 +5,9 @@ jobs:
5
5
  runs-on: ubuntu-latest
6
6
  continue-on-error: true
7
7
  steps:
8
- - uses: actions/checkout@v4
8
+ - uses: actions/checkout@v6
9
9
  - name: Install Python
10
- uses: actions/setup-python@v5
10
+ uses: actions/setup-python@v6
11
11
  with:
12
12
  python-version: "3.11"
13
13
  - name: Install dependencies
@@ -5,15 +5,21 @@ jobs:
5
5
  runs-on: ubuntu-latest
6
6
  strategy:
7
7
  matrix:
8
- python-version: ['3.10', '3.11', '3.12', '3.13']
9
- numpy-version: ['1.26', 'dev']
8
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
9
+ numpy-version: ['1.26', '2.3.5', 'dev']
10
10
  exclude:
11
+ - python-version: '3.10'
12
+ numpy-version: '2.3.5'
13
+ - python-version: '3.10'
14
+ numpy-version: 'dev'
11
15
  - python-version: '3.13'
12
16
  numpy-version: '1.26'
13
- fail-fast: true
17
+ - python-version: '3.14'
18
+ numpy-version: '1.26'
19
+ fail-fast: false
14
20
  steps:
15
- - uses: actions/checkout@v4
16
- - uses: actions/setup-python@v5
21
+ - uses: actions/checkout@v6
22
+ - uses: actions/setup-python@v6
17
23
  with:
18
24
  python-version: ${{ matrix.python-version }}
19
25
  - name: Install Dependencies
@@ -22,9 +28,10 @@ jobs:
22
28
  if [[ "${{ matrix.numpy-version }}" == "dev" ]]; then
23
29
  python -m pip install --pre --extra-index https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy;
24
30
  else
25
- python -m pip install 'numpy>=1.26,<2.0';
31
+ python -m pip install 'numpy=='${{ matrix.numpy-version }}
26
32
  fi
27
- python -m pip install -r requirements-dev.txt
33
+ python -m pip install pytest hypothesis
34
+ python -c'import numpy as np; print(f"{np.__version__ = }")'
28
35
  - name: Run Tests
29
36
  run: |
30
37
  pytest
@@ -1,6 +1,95 @@
1
1
  # Changelog
2
2
 
3
- ## 2.4.0 (XXX)
3
+ ## 2.5 (2026-02-23)
4
+
5
+ This release targets the 2025.12 revision of the Array API standard, and supports
6
+ Python versions 3.10--3.14.
7
+
8
+ ### Major Changes
9
+
10
+ - The default version of the array API standard is now 2025.12. Previous versions can
11
+ still be enabled via the [flags API](array-api-strict-flags), e.g., by calling
12
+ `set_array_api_strict_flags(api_version='2024.12')` or setting
13
+ `ARRAY_API_STRICT_API_VERSION=2024.12`.
14
+
15
+ Note that this support is still relatively undertested. Please [report any
16
+ issues](https://github.com/data-apis/array-api-strict/issues) you find.
17
+
18
+ - The following functions, new in the 2025.12 revision, are implemented:
19
+
20
+ - `isin`
21
+ - `broadcast_shapes`
22
+ - `linalg.eig`
23
+ - `linalg.eigvals`
24
+
25
+ The following updates to existing APIs have been implemented, per the 2025.12 Array API
26
+ standard revision:
27
+
28
+ - `searchsorted` now accepts a python scalar for its second argument,
29
+ `x2`.
30
+ - `expand_dims` now accepts tuples of axes.
31
+ - `permute_dims` now accepts negative axes, with the usual meaning (`axis=-1` is
32
+ the last axis, `-2` is the second-to-last axis, and so on).
33
+
34
+ The following functions now return tuples instead of lists:
35
+
36
+ - `__array_namespace_info__().devices`
37
+ - `broadcast_arrays`
38
+ - `meshgrid`
39
+
40
+
41
+ - Array objects can now be pickled.
42
+
43
+
44
+ ### Minor Changes
45
+
46
+ - Several functions were diverging from the standard, and have been fixed to follow
47
+ the 2025.12 revision of the standard:
48
+
49
+ - `clip` has been updated to accept python `int` scalars for its `min` and `max`
50
+ arguments for clipping a floating-point array.
51
+ - `clip` has been updated to always returns a copy of an input array.
52
+ - `full` and `full_like` now reject array values for the `fill_value` argument.
53
+ - `expand_dims` accepts its axis argument as either positional or keyword argument.
54
+
55
+ - `sign` of a complex zero, `sign(0 + 0j)` has been fixed to return zero.
56
+
57
+
58
+ ### Contributors
59
+
60
+ The following users contributed to this release:
61
+
62
+ Evgeni Burovski,
63
+ Lucas Colley,
64
+ Lucy Liu
65
+
66
+
67
+ ## 2.4.1 (2025-07-27)
68
+
69
+ ### Major Changes
70
+
71
+ - The array object defines `__array__` again when `__buffer__` is not available.
72
+
73
+ - Support for Python versions 3.10 and 3.11 has been reinstated.
74
+
75
+ ### Minor Changes
76
+
77
+ - Arithmetic operations no longer accept NumPy arrays.
78
+
79
+ - Disallow `__setitem__` for invalid dtype combinations (e.g. setting a float value
80
+ into an integer array)
81
+
82
+
83
+ ### Contributors
84
+
85
+ The following users contributed to this release:
86
+
87
+ Evgeni Burovski,
88
+ Lucas Colley,
89
+ Guido Imperiale
90
+
91
+
92
+ ## 2.4.0 (2025-06-16)
4
93
 
5
94
  ### Major Changes
6
95
 
@@ -39,7 +128,7 @@ The following users contributed to this release:
39
128
 
40
129
  Lumir Balhar,
41
130
  Evgeni Burovski,
42
- Joren Hammudoglu
131
+ Joren Hammudoglu,
43
132
  Tim Head,
44
133
  Guido Imperiale,
45
134
  Lucy Liu
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: array_api_strict
3
- Version: 2.4
3
+ Version: 2.5
4
4
  Summary: A strict, minimal implementation of the Python array API standard.
5
5
  Author: Consortium for Python Data API Standards
6
6
  License: Copyright (c) 2021-2024, NumPy Developers, Consortium for Python Data API Standards
@@ -48,6 +48,9 @@ Requires-Python: >=3.10
48
48
  Description-Content-Type: text/markdown
49
49
  License-File: LICENSE
50
50
  Requires-Dist: numpy
51
+ Provides-Extra: test
52
+ Requires-Dist: pytest; extra == "test"
53
+ Requires-Dist: hypothesis; extra == "test"
51
54
 
52
55
  # array-api-strict
53
56
 
@@ -29,3 +29,19 @@ array_api_tests/test_statistical_functions.py::test_sum
29
29
 
30
30
  array_api_tests/test_special_cases.py::test_nan_propagation[cumulative_prod]
31
31
 
32
+ # complex special cases which failed "forever"
33
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is +0) -> +infinity + 0j]
34
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is -infinity and imag(x_i) is +infinity) -> -1 + 0j]
35
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is +infinity) -> infinity + NaN j]
36
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is -infinity and imag(x_i) is NaN) -> -1 + 0j]
37
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is +infinity and imag(x_i) is NaN) -> infinity + NaN j]
38
+ array_api_tests/test_special_cases.py::test_unary[expm1(real(x_i) is NaN and imag(x_i) is +0) -> NaN + 0j]
39
+ array_api_tests/test_special_cases.py::test_unary[expm1((real(x_i) is +0 or real(x_i) == -0) and imag(x_i) is +0) -> 0 + 0j]
40
+
41
+ array_api_tests/test_special_cases.py::test_unary[sign((real(x_i) is -0 or real(x_i) == +0) and (imag(x_i) is -0 or imag(x_i) == +0)) -> 0 + 0j]
42
+ array_api_tests/test_special_cases.py::test_unary[tanh(real(x_i) is +infinity and isfinite(imag(x_i)) and imag(x_i) > 0) -> 1 + 0j]
43
+
44
+ # this acosh failure is only seen with python==3.10 and numpy==2.2.6, and not e.g. python 3.12 & numpy 2.4.1
45
+ array_api_tests/test_special_cases.py::test_unary[acosh(real(x_i) is +0 and imag(x_i) is NaN) -> NaN \xb1 \u03c0j/2]
46
+
47
+ array_api_tests/test_special_cases.py::test_unary[sqrt(real(x_i) is +infinity and isfinite(imag(x_i)) and imag(x_i) > 0) -> +0 + infinity j]
@@ -73,6 +73,7 @@ __all__ += [
73
73
  from ._data_type_functions import (
74
74
  astype,
75
75
  broadcast_arrays,
76
+ broadcast_shapes,
76
77
  broadcast_to,
77
78
  can_cast,
78
79
  finfo,
@@ -84,6 +85,7 @@ from ._data_type_functions import (
84
85
  __all__ += [
85
86
  "astype",
86
87
  "broadcast_arrays",
88
+ "broadcast_shapes",
87
89
  "broadcast_to",
88
90
  "can_cast",
89
91
  "finfo",
@@ -299,9 +301,9 @@ from ._searching_functions import argmax, argmin, nonzero, count_nonzero, search
299
301
 
300
302
  __all__ += ["argmax", "argmin", "nonzero", "count_nonzero", "searchsorted", "where"]
301
303
 
302
- from ._set_functions import unique_all, unique_counts, unique_inverse, unique_values
304
+ from ._set_functions import unique_all, unique_counts, unique_inverse, unique_values, isin
303
305
 
304
- __all__ += ["unique_all", "unique_counts", "unique_inverse", "unique_values"]
306
+ __all__ += ["unique_all", "unique_counts", "unique_inverse", "unique_values", "isin"]
305
307
 
306
308
  from ._sorting_functions import argsort, sort
307
309
 
@@ -16,10 +16,11 @@ of ndarray.
16
16
  from __future__ import annotations
17
17
 
18
18
  import operator
19
+ import sys
19
20
  from collections.abc import Iterator
20
21
  from enum import IntEnum
21
22
  from types import EllipsisType, ModuleType
22
- from typing import Any, Final, Literal, SupportsIndex
23
+ from typing import Any, Final, Literal, SupportsIndex, Callable
23
24
 
24
25
  import numpy as np
25
26
  import numpy.typing as npt
@@ -67,8 +68,6 @@ class Device:
67
68
  CPU_DEVICE = Device()
68
69
  ALL_DEVICES = (CPU_DEVICE, Device("device1"), Device("device2"))
69
70
 
70
- _default = object()
71
-
72
71
 
73
72
  class Array:
74
73
  """
@@ -126,6 +125,9 @@ class Array:
126
125
  raise TypeError(
127
126
  "The array_api_strict Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead."
128
127
  )
128
+
129
+ def __reduce__(self) -> tuple[Callable, tuple[npt.NDArray[Any], Device]]:
130
+ return (self._new, (self._array, self._device))
129
131
 
130
132
  # These functions are not required by the spec, but are implemented for
131
133
  # the sake of usability.
@@ -149,29 +151,40 @@ class Array:
149
151
 
150
152
  __str__ = __repr__
151
153
 
152
- # `__array__` was implemented historically for compatibility, and removing it has
153
- # caused issues for some libraries (see
154
- # https://github.com/data-apis/array-api-strict/issues/67).
155
-
156
- # Instead of `__array__` we now implement the buffer protocol.
157
- # Note that it makes array-apis-strict requiring python>=3.12
158
154
  def __buffer__(self, flags):
159
155
  if self._device != CPU_DEVICE:
160
- raise RuntimeError(f"Can not convert array on the '{self._device}' device to a Numpy array.")
156
+ raise RuntimeError(
157
+ # NumPy swallows this exception and falls back to __array__.
158
+ f"Can't extract host buffer from array on the '{self._device}' device."
159
+ )
161
160
  return self._array.__buffer__(flags)
162
161
 
163
- # We do not define __release_buffer__, per the discussion at
164
- # https://github.com/data-apis/array-api-strict/pull/115#pullrequestreview-2917178729
165
-
166
- def __array__(self, *args, **kwds):
167
- # a stub for python < 3.12; otherwise numpy silently produces object arrays
168
- import sys
169
- minor, major = sys.version_info.minor, sys.version_info.major
170
- if major < 3 or minor < 12:
162
+ # `__array__` is not part of the Array API. Ideally we want to support
163
+ # `xp.asarray(Array)` exclusively through the __buffer__ protocol; however this is
164
+ # only possible on Python >=3.12. Additionally, when __buffer__ raises (e.g. because
165
+ # the array is not on the CPU device, NumPy will try to fall back on __array__ but,
166
+ # if that doesn't exist, create a scalar numpy array of objects which contains the
167
+ # array_api_strict.Array. So we can't get rid of __array__ entirely.
168
+ def __array__(
169
+ self, dtype: None | np.dtype[Any] = None, copy: None | bool = None
170
+ ) -> npt.NDArray[Any]:
171
+ if self._device != CPU_DEVICE:
172
+ # We arrive here from np.asarray() on Python >=3.12 when __buffer__ raises.
173
+ raise RuntimeError(
174
+ f"Can't convert array on the '{self._device}' device to a "
175
+ "NumPy array."
176
+ )
177
+ if sys.version_info >= (3, 12):
171
178
  raise TypeError(
172
- "Interoperation with NumPy requires python >= 3.12. Please upgrade."
179
+ "The __array__ method is not supported by the Array API. "
180
+ "Please use the __buffer__ interface instead."
173
181
  )
174
182
 
183
+ # copy keyword is new in 2.0
184
+ if np.__version__[0] < '2':
185
+ return np.asarray(self._array, dtype=dtype)
186
+ return np.asarray(self._array, dtype=dtype, copy=copy)
187
+
175
188
  # These are various helper functions to make the array behavior match the
176
189
  # spec in places where it either deviates from or is more strict than
177
190
  # NumPy behavior
@@ -942,6 +955,15 @@ class Array:
942
955
  self._validate_index(key, op="setitem")
943
956
  # Indexing self._array with array_api_strict arrays can be erroneous
944
957
  np_key = key._array if isinstance(key, Array) else key
958
+
959
+ # sanitize the value
960
+ other = value
961
+ if isinstance(value, (bool, int, float, complex)):
962
+ other = self._promote_scalar(value)
963
+ dt = _result_type(self.dtype, other.dtype)
964
+ if dt != self.dtype:
965
+ raise TypeError(f"mismatched dtypes: {self.dtype = } and {other.dtype = }")
966
+
945
967
  self._array.__setitem__(np_key, asarray(value)._array)
946
968
 
947
969
  def __sub__(self, other: Array | complex, /) -> Array:
@@ -1104,6 +1126,7 @@ class Array:
1104
1126
  """
1105
1127
  Performs the operation __imod__.
1106
1128
  """
1129
+ self._check_type_device(other)
1107
1130
  other = self._check_allowed_dtypes(other, "real numeric", "__imod__")
1108
1131
  if other is NotImplemented:
1109
1132
  return other
@@ -1126,6 +1149,7 @@ class Array:
1126
1149
  """
1127
1150
  Performs the operation __imul__.
1128
1151
  """
1152
+ self._check_type_device(other)
1129
1153
  other = self._check_allowed_dtypes(other, "numeric", "__imul__")
1130
1154
  if other is NotImplemented:
1131
1155
  return other
@@ -1148,6 +1172,7 @@ class Array:
1148
1172
  """
1149
1173
  Performs the operation __ior__.
1150
1174
  """
1175
+ self._check_type_device(other)
1151
1176
  other = self._check_allowed_dtypes(other, "integer or boolean", "__ior__")
1152
1177
  if other is NotImplemented:
1153
1178
  return other
@@ -1170,6 +1195,7 @@ class Array:
1170
1195
  """
1171
1196
  Performs the operation __ipow__.
1172
1197
  """
1198
+ self._check_type_device(other)
1173
1199
  other = self._check_allowed_dtypes(other, "numeric", "__ipow__")
1174
1200
  if other is NotImplemented:
1175
1201
  return other
@@ -1182,6 +1208,7 @@ class Array:
1182
1208
  """
1183
1209
  from ._elementwise_functions import pow # type: ignore[attr-defined]
1184
1210
 
1211
+ self._check_type_device(other)
1185
1212
  other = self._check_allowed_dtypes(other, "numeric", "__rpow__")
1186
1213
  if other is NotImplemented:
1187
1214
  return other
@@ -1193,6 +1220,7 @@ class Array:
1193
1220
  """
1194
1221
  Performs the operation __irshift__.
1195
1222
  """
1223
+ self._check_type_device(other)
1196
1224
  other = self._check_allowed_dtypes(other, "integer", "__irshift__")
1197
1225
  if other is NotImplemented:
1198
1226
  return other
@@ -1215,6 +1243,7 @@ class Array:
1215
1243
  """
1216
1244
  Performs the operation __isub__.
1217
1245
  """
1246
+ self._check_type_device(other)
1218
1247
  other = self._check_allowed_dtypes(other, "numeric", "__isub__")
1219
1248
  if other is NotImplemented:
1220
1249
  return other
@@ -1237,6 +1266,7 @@ class Array:
1237
1266
  """
1238
1267
  Performs the operation __itruediv__.
1239
1268
  """
1269
+ self._check_type_device(other)
1240
1270
  other = self._check_allowed_dtypes(other, "floating-point", "__itruediv__")
1241
1271
  if other is NotImplemented:
1242
1272
  return other
@@ -1259,6 +1289,7 @@ class Array:
1259
1289
  """
1260
1290
  Performs the operation __ixor__.
1261
1291
  """
1292
+ self._check_type_device(other)
1262
1293
  other = self._check_allowed_dtypes(other, "integer or boolean", "__ixor__")
1263
1294
  if other is NotImplemented:
1264
1295
  return other
@@ -240,8 +240,9 @@ def full(
240
240
  _check_valid_dtype(dtype)
241
241
  _check_device(device)
242
242
 
243
- if isinstance(fill_value, Array) and fill_value.ndim == 0:
244
- fill_value = fill_value._array
243
+ if not isinstance(fill_value, bool | int | float | complex):
244
+ msg = f"Expected Python scalar fill_value, got type {type(fill_value)}"
245
+ raise TypeError(msg)
245
246
  res = np.full(shape, fill_value, dtype=_np_dtype(dtype))
246
247
  if DType(res.dtype) not in _all_dtypes:
247
248
  # This will happen if the fill value is not something that NumPy
@@ -270,6 +271,10 @@ def full_like(
270
271
  if device is None:
271
272
  device = x.device
272
273
 
274
+ if not isinstance(fill_value, bool | int | float | complex):
275
+ msg = f"Expected Python scalar fill_value, got type {type(fill_value)}"
276
+ raise TypeError(msg)
277
+
273
278
  res = np.full_like(x._array, fill_value, dtype=_np_dtype(dtype))
274
279
  if DType(res.dtype) not in _all_dtypes:
275
280
  # This will happen if the fill value is not something that NumPy
@@ -304,7 +309,7 @@ def linspace(
304
309
  )
305
310
 
306
311
 
307
- def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") -> list[Array]:
312
+ def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") -> tuple[Array, ...]:
308
313
  """
309
314
  Array API compatible wrapper for :py:func:`np.meshgrid <numpy.meshgrid>`.
310
315
 
@@ -327,10 +332,12 @@ def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") -> list[Array
327
332
  else:
328
333
  device = None
329
334
 
330
- return [
335
+ typ = list if get_array_api_strict_flags()['api_version'] < '2025.12' else tuple
336
+
337
+ return typ(
331
338
  Array._new(array, device=device)
332
339
  for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)
333
- ]
340
+ )
334
341
 
335
342
 
336
343
  def ones(
@@ -16,7 +16,7 @@ from ._dtypes import (
16
16
  _signed_integer_dtypes,
17
17
  _unsigned_integer_dtypes,
18
18
  )
19
- from ._flags import get_array_api_strict_flags
19
+ from ._flags import get_array_api_strict_flags, requires_api_version
20
20
 
21
21
 
22
22
  # Note: astype is a function, not an array method as in NumPy.
@@ -49,7 +49,7 @@ def astype(
49
49
  return Array._new(x._array.astype(dtype=dtype._np_dtype, copy=copy), device=device)
50
50
 
51
51
 
52
- def broadcast_arrays(*arrays: Array) -> list[Array]:
52
+ def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]:
53
53
  """
54
54
  Array API compatible wrapper for :py:func:`np.broadcast_arrays <numpy.broadcast_arrays>`.
55
55
 
@@ -57,9 +57,21 @@ def broadcast_arrays(*arrays: Array) -> list[Array]:
57
57
  """
58
58
  from ._array_object import Array
59
59
 
60
- return [
60
+ typ = list if get_array_api_strict_flags()['api_version'] < '2025.12' else tuple
61
+
62
+ return typ(
61
63
  Array._new(array, device=arrays[0].device) for array in np.broadcast_arrays(*[a._array for a in arrays])
62
- ]
64
+ )
65
+
66
+
67
+ @requires_api_version("2025.12")
68
+ def broadcast_shapes(*shapes: tuple[int, ...]) -> tuple[int, ...]:
69
+ """
70
+ Array API compatible wrapper for :py:func:`np.broadcast_shapes <numpy.broadcast_shapes>`.
71
+
72
+ See its docstring for more information.
73
+ """
74
+ return np.broadcast_shapes(*shapes)
63
75
 
64
76
 
65
77
  def broadcast_to(x: Array, /, shape: tuple[int, ...]) -> Array:
@@ -257,7 +257,7 @@ def clip(
257
257
  raise TypeError("Only real numeric dtypes are allowed in clip")
258
258
 
259
259
  if min is max is None:
260
- return x
260
+ return Array._new(x._array.copy(), device=x.device)
261
261
 
262
262
  for argname, arg in ("min", min), ("max", max):
263
263
  if isinstance(arg, Array):
@@ -278,8 +278,8 @@ def clip(
278
278
  isinstance(arg, Array) and arg.dtype in _real_floating_dtypes)):
279
279
  raise TypeError(f"{argname} must be integral when x is integral")
280
280
  if (x.dtype in _real_floating_dtypes
281
- and (isinstance(arg, int) or
282
- isinstance(arg, Array) and arg.dtype in _integer_dtypes)):
281
+ and (isinstance(arg, Array) and arg.dtype in _integer_dtypes)
282
+ ):
283
283
  raise TypeError(f"{arg} must be floating-point when x is floating-point")
284
284
 
285
285
  # Normalize to make the below logic simpler
@@ -352,5 +352,7 @@ def sign(x: Array, /) -> Array:
352
352
  raise TypeError("Only numeric dtypes are allowed in sign")
353
353
  # Special treatment to work around non-compliant NumPy 1.x behaviour
354
354
  if x.dtype in _complex_floating_dtypes:
355
- return x/abs(x)
355
+ _x = x._array
356
+ _result = _x / np.abs(np.where(_x != 0, _x, np.asarray(1.0, dtype=_x.dtype)))
357
+ return Array._new(_result, device=x.device)
356
358
  return Array._new(np.sign(x._array), device=x.device)
@@ -30,11 +30,12 @@ supported_versions = (
30
30
  "2022.12",
31
31
  "2023.12",
32
32
  "2024.12",
33
+ "2025.12",
33
34
  )
34
35
 
35
- draft_version = "2025.12"
36
+ draft_version = "2026.12"
36
37
 
37
- API_VERSION = default_version = "2024.12"
38
+ API_VERSION = default_version = "2025.12"
38
39
 
39
40
  BOOLEAN_INDEXING = True
40
41