Repository: burnash/gspread

Issue:
Feature request: Support named parameters to permit easier transition to 6.x API
**Is your feature request related to a problem? Please describe.**
Since around v5.10, we have started seeing `DeprecationWarning` notices. For example, a call such as `worksheet.update('A1', [['foo']])` results in the warning:

> `method signature will change to: 'Worksheet.update(value = [[]], range_name=) arguments 'range_name' and 'values' will swap, values will be mandatory of type: 'list(list(...))`

We want to update our 5.x code to support the new 6.x API when it arrives. However it seems that it is currently _not possible_ to use named parameters for some functions. If we specify named parameters like `worksheet.update(range_name='A1', values=[['foo']])` then we get an error:

> `update got unexpected keyword arguments: ['range_name', 'values']`

It appears that this is because of the `@accepted_kwargs` decorator on many functions that implements defaults. As far as I understand it, these only define *optional* parameters (and their defaults), and not *required* parameters. So if we use named required parameters, they won't pass the `@accepted_kwargs` decorator check.

**Describe the solution you'd like**
The goal is to allow us to support named parameters in a 5.x release, so that we can easily transition to the 6.x API. E.g. we should be able to use `worksheet.update(range_name='A1', values=[['foo']])` in a 5.x release

To implement this, my suggestion is to add the required params to the `@accepted_kwargs` decorator with a sentinel value, e.g.

```python
    @accepted_kwargs(
        range_name=REQUIRED,
        values=None,
        raw=True,
        major_dimension=None,
        ...)
     def update(self, range_name, **kwargs):
```

And update the `@accepted_kwargs` decorator to handle defaults a little differently:

```python
              for k, v in default_kwargs.items():
                   if v != REQUIRED:  # Ignore setting defaults for REQUIRED kwargs.
                      kwargs.setdefault(k, v)
```

Discussion:

Patch:
diff --git a/gspread/utils.py b/gspread/utils.py
index 6886a31..1ff79b9 100644
--- a/gspread/utils.py
+++ b/gspread/utils.py
@@ -89,6 +89,8 @@ DEPRECATION_WARNING_TEMPLATE = (
     "[Deprecated][in version {v_deprecated}]: {msg_deprecated}"
 )
 
+REQUIRED_KWARGS = "required"
+
 
 def convert_credentials(credentials):
     module = credentials.__module__
@@ -723,7 +725,8 @@ def accepted_kwargs(**default_kwargs):
                 raise TypeError(err % (f.__name__, list(unexpected_kwargs)))
 
             for k, v in default_kwargs.items():
-                kwargs.setdefault(k, v)
+                if v != REQUIRED_KWARGS:
+                    kwargs.setdefault(k, v)
 
             return f(*args, **kwargs)
 
diff --git a/gspread/worksheet.py b/gspread/worksheet.py
index 606b71a..68a398c 100644
--- a/gspread/worksheet.py
+++ b/gspread/worksheet.py
@@ -13,6 +13,7 @@ from .exceptions import GSpreadException
 from .urls import SPREADSHEET_URL, WORKSHEET_DRIVE_URL
 from .utils import (
     DEPRECATION_WARNING_TEMPLATE,
+    REQUIRED_KWARGS,
     Dimension,
     PasteOrientation,
     PasteType,
@@ -933,7 +934,12 @@ class Worksheet:
 
         return [ValueRange.from_json(x) for x in response["valueRanges"]]
 
+    # required kwargs is a special value just to allow users to use kwargs
+    # and regular args mixed in order to transit to version 6.0.0
+    # This will be be entirely removed in version 6.0.0
     @accepted_kwargs(
+        range_name=REQUIRED_KWARGS,
+        values=REQUIRED_KWARGS,
         raw=True,
         major_dimension=None,
         value_input_option=None,


Test patch:
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 727ed8f..02656d7 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -274,3 +274,28 @@ class UtilsTest(unittest.TestCase):
         actual = utils.fill_gaps(matrix, 3, 6)
 
         self.assertEqual(actual, expected)
+
+    def test_accepted_kwargs(self):
+        """test accepted_kwargs function.
+        Test the temporary special value: REQUIRED_KWARGS
+        """
+
+        expected_arg0 = 0
+        expected_arg1 = 1
+
+        @utils.accepted_kwargs(arg1=1)
+        def sample_arg1(arg0, **kwargs):
+            self.assertEqual(arg0, expected_arg0)
+            self.assertEqual(kwargs["arg1"], expected_arg1)
+
+        sample_arg1(0)
+
+        expected_arg2 = 2
+
+        @utils.accepted_kwargs(arg1=utils.REQUIRED_KWARGS, arg2=2)
+        def sample_arg2(arg0, arg1=None, **kwargs):
+            self.assertEqual(arg0, expected_arg0)
+            self.assertEqual(arg1, expected_arg1)
+            self.assertEqual(kwargs["arg2"], expected_arg2)
+
+        sample_arg2(0, arg1=1, arg2=2)