Repository: zarr-developers/zarr-python

Issue:
[v3] `Group.{zeros, ones, etc.}` should accept positional `name` arguments
### Zarr version

3.0.0.beta

### Numcodecs version

0.13

### Python Version

3.11

### Operating System

Mac

### Installation

pip

### Description

In v2, Zarr-Python Groups supported a single positional argument (`name`) in the following methods:

`Group.create`
`Group.empty`
`Group.zeros`
`Group.ones`
`Group.full`
`Group.array`

and two positional arguments (`name` + `data`) in:

`Group.empty_like`
`Group.zeros_like`
`Group.ones_like`
`Group.full_like`

I like the API we've gone with but we should go through a deprecation cycle before enforcing the keyword only interface. Thanks to @TomAugspurger for adding `@_deprecate_positional_args` for this.

### Steps to reproduce

```python
In [1]: import zarr

In [2]: zarr.__version__
Out[2]: '3.0.0b0'

In [3]: root = zarr.group()

In [4]: foo = root.create_group('foo')

In [5]: bar = foo.create_group('bar')

In [6]: z1 = bar.zeros('baz', shape=(10000, 10000), chunks=(1000, 1000), dtype='i4')
--------------------------------------------------------------------------
TypeError                                Traceback (most recent call last)
Cell In[6], line 1
----> 1 z1 = bar.zeros('baz', shape=(10000, 10000), chunks=(1000, 1000), dtype='i4')

TypeError: Group.zeros() takes 1 positional argument but 2 positional arguments (and 1 keyword-only argument) were given
```

### Additional output

_No response_

Discussion:

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index bb3d4336..00000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
-  push:
-    branches: [ "main" ]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [ "main" ]
-  schedule:
-    - cron: '29 0 * * 1'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-    permissions:
-      actions: read
-      contents: read
-      security-events: write
-
-    strategy:
-      fail-fast: false
-      matrix:
-        language: [ 'python' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
-        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v4
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v3
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file.
-        # Prefix the list here with "+" to use these queries and those in the config file.
-        
-        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
-        # queries: security-extended,security-and-quality
-
-        
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v3
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
-    #   If the Autobuild fails above, remove it and uncomment the following three lines. 
-    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
-    # - run: |
-    #   echo "Run, Build Application using script"
-    #   ./location_of_script_within_repo/buildscript.sh
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v3
diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py
index ba682135..6e54b7ec 100644
--- a/src/zarr/core/group.py
+++ b/src/zarr/core/group.py
@@ -14,6 +14,7 @@ import numpy.typing as npt
 from typing_extensions import deprecated
 
 import zarr.api.asynchronous as async_api
+from zarr._compat import _deprecate_positional_args
 from zarr.abc.metadata import Metadata
 from zarr.abc.store import Store, set_or_delete
 from zarr.core.array import Array, AsyncArray, _build_parents
@@ -1531,6 +1532,7 @@ class Group(SyncMixin):
         # Backwards compatibility for 2.x
         return self.create_array(*args, **kwargs)
 
+    @_deprecate_positional_args
     def create_array(
         self,
         name: str,
@@ -1692,15 +1694,19 @@ class Group(SyncMixin):
         """
         return Array(self._sync(self._async_group.require_array(name, **kwargs)))
 
+    @_deprecate_positional_args
     def empty(self, *, name: str, shape: ChunkCoords, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.empty(name=name, shape=shape, **kwargs)))
 
+    @_deprecate_positional_args
     def zeros(self, *, name: str, shape: ChunkCoords, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.zeros(name=name, shape=shape, **kwargs)))
 
+    @_deprecate_positional_args
     def ones(self, *, name: str, shape: ChunkCoords, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.ones(name=name, shape=shape, **kwargs)))
 
+    @_deprecate_positional_args
     def full(
         self, *, name: str, shape: ChunkCoords, fill_value: Any | None, **kwargs: Any
     ) -> Array:
@@ -1710,15 +1716,19 @@ class Group(SyncMixin):
             )
         )
 
+    @_deprecate_positional_args
     def empty_like(self, *, name: str, data: async_api.ArrayLike, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.empty_like(name=name, data=data, **kwargs)))
 
+    @_deprecate_positional_args
     def zeros_like(self, *, name: str, data: async_api.ArrayLike, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.zeros_like(name=name, data=data, **kwargs)))
 
+    @_deprecate_positional_args
     def ones_like(self, *, name: str, data: async_api.ArrayLike, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.ones_like(name=name, data=data, **kwargs)))
 
+    @_deprecate_positional_args
     def full_like(self, *, name: str, data: async_api.ArrayLike, **kwargs: Any) -> Array:
         return Array(self._sync(self._async_group.full_like(name=name, data=data, **kwargs)))
 
@@ -1726,6 +1736,7 @@ class Group(SyncMixin):
         return self._sync(self._async_group.move(source, dest))
 
     @deprecated("Use Group.create_array instead.")
+    @_deprecate_positional_args
     def array(
         self,
         name: str,


Test patch:
diff --git a/tests/test_group.py b/tests/test_group.py
index f4063f6e..21e4ef4e 100644
--- a/tests/test_group.py
+++ b/tests/test_group.py
@@ -1334,3 +1334,23 @@ def test_update_attrs() -> None:
     )
     root.attrs["foo"] = "bar"
     assert root.attrs["foo"] == "bar"
+
+
+@pytest.mark.parametrize("method", ["empty", "zeros", "ones", "full"])
+def test_group_deprecated_positional_args(method: str) -> None:
+    if method == "full":
+        kwargs = {"fill_value": 0}
+    else:
+        kwargs = {}
+
+    root = zarr.group()
+    with pytest.warns(FutureWarning, match=r"Pass name=.* as keyword args."):
+        arr = getattr(root, method)("foo", shape=1, **kwargs)
+        assert arr.shape == (1,)
+
+    method += "_like"
+    data = np.ones(1)
+
+    with pytest.warns(FutureWarning, match=r"Pass name=.*, data=.* as keyword args."):
+        arr = getattr(root, method)("foo_like", data, **kwargs)
+        assert arr.shape == data.shape