prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  `APIDataSet` ease of use
  ## Description
  
  Howdy team!
  
  I was working with the `APIDataSet` recently and had two issues out of the box.
  
  #### 1. Specifying the `auth` keyword argument in yaml
  
  The `requests` library expects the `auth` parameter of a request to be either a `HTTPBasicAuth` or a `tuple` (lists are not allowed, see [here](https://github.com/ianwhale/kedro-kaggle-starter/blob/master/%7B%7B%20cookiecutter.repo_name%20%7D%7D/src/%7B%7B%20cookiecutter.python_package%20%7D%7D/api.py) in requests). At the moment, neither are possible to specify in my `catalog.yml`. 
  
  From what I hear, you're already working on this (#1011). So maybe this point is moot.
  
  #### 2. The `auth` keyword argument and `credentials.yml`
  
  I would like to specify my `(username, password)` tuple inside `credentials.yml`. However, the `APIDataSet`'s `auth` keyword wouldn't get filled in by the config loader. 
  
  To get this working, you'd have to extend `APIDataSet` to have a `credentials` keyword that is filled in for `auth` in an upcall.
  
  It would be great to either have this by default, or even have the loader fill `auth` keywords in addition to `credentials`. Although that might have unintended consequences. 
  
  ## Context
  
  Hopefully this would unify the experience a bit. Right now, the `credentials` keyword in a dataset and `credentials.yml` are the main points of access to secrets. Which is probably good.
  
  ## Possible Implementation
  
  I whipped up [my own `APIDataSet`](https://github.com/ianwhale/kedro-kaggle-starter/blob/master/%7B%7B%20cookiecutter.repo_name%20%7D%7D/src/%7B%7B%20cookiecutter.python_package%20%7D%7D/api.py) to solve both the problems above.
  
  ## Possible Alternatives
  
  To get this working with no changes to `APIDataSet`, we'd have to implement the changes in #1011 so we can specify tuples in `credentials.yml` and have the config loader fill in `auth` as well.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `kedro/extras/datasets/api/api_dataset.py`
  Content:
  ```
  1 """``APIDataSet`` loads the data from HTTP(S) APIs.
  2 It uses the python requests library: https://requests.readthedocs.io/en/master/
  3 """
  4 from typing import Any, Dict, List, Tuple, Union
  5 
  6 import requests
  7 from requests.auth import AuthBase
  8 
  9 from kedro.io.core import AbstractDataSet, DataSetError
  10 
  11 
  12 class APIDataSet(AbstractDataSet):
  13     """``APIDataSet`` loads the data from HTTP(S) APIs.
  14     It uses the python requests library: https://requests.readthedocs.io/en/master/
  15 
  16     Example:
  17     ::
  18 
  19         >>> from kedro.extras.datasets.api import APIDataSet
  20         >>>
  21         >>>
  22         >>> data_set = APIDataSet(
  23         >>>     url="https://quickstats.nass.usda.gov",
  24         >>>     params={
  25         >>>         "key": "SOME_TOKEN",
  26         >>>         "format": "JSON",
  27         >>>         "commodity_desc": "CORN",
  28         >>>         "statisticcat_des": "YIELD",
  29         >>>         "agg_level_desc": "STATE",
  30         >>>         "year": 2000
  31         >>>     }
  32         >>> )
  33         >>> data = data_set.load()
  34     """
  35 
  36     # pylint: disable=too-many-arguments
  37     def __init__(
  38         self,
  39         url: str,
  40         method: str = "GET",
  41         data: Any = None,
  42         params: Dict[str, Any] = None,
  43         headers: Dict[str, Any] = None,
  44         auth: Union[Tuple[str], AuthBase] = None,
  45         json: Union[List, Dict[str, Any]] = None,
  46         timeout: int = 60,
  47     ) -> None:
  48         """Creates a new instance of ``APIDataSet`` to fetch data from an API endpoint.
  49 
  50         Args:
  51             url: The API URL endpoint.
  52             method: The Method of the request, GET, POST, PUT, DELETE, HEAD, etc...
  53             data: The request payload, used for POST, PUT, etc requests
  54                 https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests
  55             params: The url parameters of the API.
  56                 https://requests.readthedocs.io/en/master/user/quickstart/#passing-parameters-in-urls
  57             headers: The HTTP headers.
  58                 https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers
  59             auth: Anything ``requests`` accepts. Normally it's either ``('login', 'password')``,
  60                 or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases.
  61             json: The request payload, used for POST, PUT, etc requests, passed in
  62                 to the json kwarg in the requests object.
  63                 https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests
  64             timeout: The wait time in seconds for a response, defaults to 1 minute.
  65                 https://requests.readthedocs.io/en/master/user/quickstart/#timeouts
  66 
  67         """
  68         super().__init__()
  69         self._request_args: Dict[str, Any] = {
  70             "url": url,
  71             "method": method,
  72             "data": data,
  73             "params": params,
  74             "headers": headers,
  75             "auth": auth,
  76             "json": json,
  77             "timeout": timeout,
  78         }
  79 
  80     def _describe(self) -> Dict[str, Any]:
  81         return dict(**self._request_args)
  82 
  83     def _execute_request(self) -> requests.Response:
  84         try:
  85             response = requests.request(**self._request_args)
  86             response.raise_for_status()
  87         except requests.exceptions.HTTPError as exc:
  88             raise DataSetError("Failed to fetch data", exc) from exc
  89         except OSError as exc:
  90             raise DataSetError("Failed to connect to the remote server") from exc
  91 
  92         return response
  93 
  94     def _load(self) -> requests.Response:
  95         return self._execute_request()
  96 
  97     def _save(self, data: Any) -> None:
  98         raise DataSetError(f"{self.__class__.__name__} is a read only data set type")
  99 
  100     def _exists(self) -> bool:
  101         response = self._execute_request()
  102 
  103         return response.ok
  104
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/kedro/extras/datasets/api/api_dataset.py b/kedro/extras/datasets/api/api_dataset.py
  --- a/kedro/extras/datasets/api/api_dataset.py
  +++ b/kedro/extras/datasets/api/api_dataset.py
  @@ -1,7 +1,7 @@
   """``APIDataSet`` loads the data from HTTP(S) APIs.
   It uses the python requests library: https://requests.readthedocs.io/en/master/
   """
  -from typing import Any, Dict, List, Tuple, Union
  +from typing import Any, Dict, Iterable, List, Union
   
   import requests
   from requests.auth import AuthBase
  @@ -41,9 +41,10 @@
           data: Any = None,
           params: Dict[str, Any] = None,
           headers: Dict[str, Any] = None,
  -        auth: Union[Tuple[str], AuthBase] = None,
  +        auth: Union[Iterable[str], AuthBase] = None,
           json: Union[List, Dict[str, Any]] = None,
           timeout: int = 60,
  +        credentials: Union[Iterable[str], AuthBase] = None,
       ) -> None:
           """Creates a new instance of ``APIDataSet`` to fetch data from an API endpoint.
   
  @@ -57,15 +58,29 @@
               headers: The HTTP headers.
                   https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers
               auth: Anything ``requests`` accepts. Normally it's either ``('login', 'password')``,
  -                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases.
  +                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases. Any
  +                iterable will be cast to a tuple.
               json: The request payload, used for POST, PUT, etc requests, passed in
                   to the json kwarg in the requests object.
                   https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests
               timeout: The wait time in seconds for a response, defaults to 1 minute.
                   https://requests.readthedocs.io/en/master/user/quickstart/#timeouts
  +            credentials: same as ``auth``. Allows specifying ``auth`` secrets in
  +                credentials.yml.
   
  +        Raises:
  +            ValueError: if both ``credentials`` and ``auth`` are specified.
           """
           super().__init__()
  +
  +        if credentials is not None and auth is not None:
  +            raise ValueError("Cannot specify both auth and credentials.")
  +
  +        auth = credentials or auth
  +
  +        if isinstance(auth, Iterable):
  +            auth = tuple(auth)
  +
           self._request_args: Dict[str, Any] = {
               "url": url,
               "method": method,
  
verification_info: {"golden_diff": "diff --git a/kedro/extras/datasets/api/api_dataset.py b/kedro/extras/datasets/api/api_dataset.py\n--- a/kedro/extras/datasets/api/api_dataset.py\n+++ b/kedro/extras/datasets/api/api_dataset.py\n@@ -1,7 +1,7 @@\n \"\"\"``APIDataSet`` loads the data from HTTP(S) APIs.\n It uses the python requests library: https://requests.readthedocs.io/en/master/\n \"\"\"\n-from typing import Any, Dict, List, Tuple, Union\n+from typing import Any, Dict, Iterable, List, Union\n \n import requests\n from requests.auth import AuthBase\n@@ -41,9 +41,10 @@\n         data: Any = None,\n         params: Dict[str, Any] = None,\n         headers: Dict[str, Any] = None,\n-        auth: Union[Tuple[str], AuthBase] = None,\n+        auth: Union[Iterable[str], AuthBase] = None,\n         json: Union[List, Dict[str, Any]] = None,\n         timeout: int = 60,\n+        credentials: Union[Iterable[str], AuthBase] = None,\n     ) -> None:\n         \"\"\"Creates a new instance of ``APIDataSet`` to fetch data from an API endpoint.\n \n@@ -57,15 +58,29 @@\n             headers: The HTTP headers.\n                 https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers\n             auth: Anything ``requests`` accepts. Normally it's either ``('login', 'password')``,\n-                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases.\n+                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases. Any\n+                iterable will be cast to a tuple.\n             json: The request payload, used for POST, PUT, etc requests, passed in\n                 to the json kwarg in the requests object.\n                 https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests\n             timeout: The wait time in seconds for a response, defaults to 1 minute.\n                 https://requests.readthedocs.io/en/master/user/quickstart/#timeouts\n+            credentials: same as ``auth``. Allows specifying ``auth`` secrets in\n+                credentials.yml.\n \n+        Raises:\n+            ValueError: if both ``credentials`` and ``auth`` are specified.\n         \"\"\"\n         super().__init__()\n+\n+        if credentials is not None and auth is not None:\n+            raise ValueError(\"Cannot specify both auth and credentials.\")\n+\n+        auth = credentials or auth\n+\n+        if isinstance(auth, Iterable):\n+            auth = tuple(auth)\n+\n         self._request_args: Dict[str, Any] = {\n             \"url\": url,\n             \"method\": method,\n", "issue": "`APIDataSet` ease of use\n## Description\r\n\r\nHowdy team!\r\n\r\nI was working with the `APIDataSet` recently and had two issues out of the box.\r\n\r\n#### 1. Specifying the `auth` keyword argument in yaml\r\n\r\nThe `requests` library expects the `auth` parameter of a request to be either a `HTTPBasicAuth` or a `tuple` (lists are not allowed, see [here](https://github.com/ianwhale/kedro-kaggle-starter/blob/master/%7B%7B%20cookiecutter.repo_name%20%7D%7D/src/%7B%7B%20cookiecutter.python_package%20%7D%7D/api.py) in requests). At the moment, neither are possible to specify in my `catalog.yml`. \r\n\r\nFrom what I hear, you're already working on this (#1011). So maybe this point is moot.\r\n\r\n#### 2. The `auth` keyword argument and `credentials.yml`\r\n\r\nI would like to specify my `(username, password)` tuple inside `credentials.yml`. However, the `APIDataSet`'s `auth` keyword wouldn't get filled in by the config loader. \r\n\r\nTo get this working, you'd have to extend `APIDataSet` to have a `credentials` keyword that is filled in for `auth` in an upcall.\r\n\r\nIt would be great to either have this by default, or even have the loader fill `auth` keywords in addition to `credentials`. Although that might have unintended consequences. \r\n\r\n## Context\r\n\r\nHopefully this would unify the experience a bit. Right now, the `credentials` keyword in a dataset and `credentials.yml` are the main points of access to secrets. Which is probably good.\r\n\r\n## Possible Implementation\r\n\r\nI whipped up [my own `APIDataSet`](https://github.com/ianwhale/kedro-kaggle-starter/blob/master/%7B%7B%20cookiecutter.repo_name%20%7D%7D/src/%7B%7B%20cookiecutter.python_package%20%7D%7D/api.py) to solve both the problems above.\r\n\r\n## Possible Alternatives\r\n\r\nTo get this working with no changes to `APIDataSet`, we'd have to implement the changes in #1011 so we can specify tuples in `credentials.yml` and have the config loader fill in `auth` as well.\n", "before_files": [{"content": "\"\"\"``APIDataSet`` loads the data from HTTP(S) APIs.\nIt uses the python requests library: https://requests.readthedocs.io/en/master/\n\"\"\"\nfrom typing import Any, Dict, List, Tuple, Union\n\nimport requests\nfrom requests.auth import AuthBase\n\nfrom kedro.io.core import AbstractDataSet, DataSetError\n\n\nclass APIDataSet(AbstractDataSet):\n    \"\"\"``APIDataSet`` loads the data from HTTP(S) APIs.\n    It uses the python requests library: https://requests.readthedocs.io/en/master/\n\n    Example:\n    ::\n\n        >>> from kedro.extras.datasets.api import APIDataSet\n        >>>\n        >>>\n        >>> data_set = APIDataSet(\n        >>>     url=\"https://quickstats.nass.usda.gov\",\n        >>>     params={\n        >>>         \"key\": \"SOME_TOKEN\",\n        >>>         \"format\": \"JSON\",\n        >>>         \"commodity_desc\": \"CORN\",\n        >>>         \"statisticcat_des\": \"YIELD\",\n        >>>         \"agg_level_desc\": \"STATE\",\n        >>>         \"year\": 2000\n        >>>     }\n        >>> )\n        >>> data = data_set.load()\n    \"\"\"\n\n    # pylint: disable=too-many-arguments\n    def __init__(\n        self,\n        url: str,\n        method: str = \"GET\",\n        data: Any = None,\n        params: Dict[str, Any] = None,\n        headers: Dict[str, Any] = None,\n        auth: Union[Tuple[str], AuthBase] = None,\n        json: Union[List, Dict[str, Any]] = None,\n        timeout: int = 60,\n    ) -> None:\n        \"\"\"Creates a new instance of ``APIDataSet`` to fetch data from an API endpoint.\n\n        Args:\n            url: The API URL endpoint.\n            method: The Method of the request, GET, POST, PUT, DELETE, HEAD, etc...\n            data: The request payload, used for POST, PUT, etc requests\n                https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests\n            params: The url parameters of the API.\n                https://requests.readthedocs.io/en/master/user/quickstart/#passing-parameters-in-urls\n            headers: The HTTP headers.\n                https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers\n            auth: Anything ``requests`` accepts. Normally it's either ``('login', 'password')``,\n                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases.\n            json: The request payload, used for POST, PUT, etc requests, passed in\n                to the json kwarg in the requests object.\n                https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests\n            timeout: The wait time in seconds for a response, defaults to 1 minute.\n                https://requests.readthedocs.io/en/master/user/quickstart/#timeouts\n\n        \"\"\"\n        super().__init__()\n        self._request_args: Dict[str, Any] = {\n            \"url\": url,\n            \"method\": method,\n            \"data\": data,\n            \"params\": params,\n            \"headers\": headers,\n            \"auth\": auth,\n            \"json\": json,\n            \"timeout\": timeout,\n        }\n\n    def _describe(self) -> Dict[str, Any]:\n        return dict(**self._request_args)\n\n    def _execute_request(self) -> requests.Response:\n        try:\n            response = requests.request(**self._request_args)\n            response.raise_for_status()\n        except requests.exceptions.HTTPError as exc:\n            raise DataSetError(\"Failed to fetch data\", exc) from exc\n        except OSError as exc:\n            raise DataSetError(\"Failed to connect to the remote server\") from exc\n\n        return response\n\n    def _load(self) -> requests.Response:\n        return self._execute_request()\n\n    def _save(self, data: Any) -> None:\n        raise DataSetError(f\"{self.__class__.__name__} is a read only data set type\")\n\n    def _exists(self) -> bool:\n        response = self._execute_request()\n\n        return response.ok\n", "path": "kedro/extras/datasets/api/api_dataset.py"}], "after_files": [{"content": "\"\"\"``APIDataSet`` loads the data from HTTP(S) APIs.\nIt uses the python requests library: https://requests.readthedocs.io/en/master/\n\"\"\"\nfrom typing import Any, Dict, Iterable, List, Union\n\nimport requests\nfrom requests.auth import AuthBase\n\nfrom kedro.io.core import AbstractDataSet, DataSetError\n\n\nclass APIDataSet(AbstractDataSet):\n    \"\"\"``APIDataSet`` loads the data from HTTP(S) APIs.\n    It uses the python requests library: https://requests.readthedocs.io/en/master/\n\n    Example:\n    ::\n\n        >>> from kedro.extras.datasets.api import APIDataSet\n        >>>\n        >>>\n        >>> data_set = APIDataSet(\n        >>>     url=\"https://quickstats.nass.usda.gov\",\n        >>>     params={\n        >>>         \"key\": \"SOME_TOKEN\",\n        >>>         \"format\": \"JSON\",\n        >>>         \"commodity_desc\": \"CORN\",\n        >>>         \"statisticcat_des\": \"YIELD\",\n        >>>         \"agg_level_desc\": \"STATE\",\n        >>>         \"year\": 2000\n        >>>     }\n        >>> )\n        >>> data = data_set.load()\n    \"\"\"\n\n    # pylint: disable=too-many-arguments\n    def __init__(\n        self,\n        url: str,\n        method: str = \"GET\",\n        data: Any = None,\n        params: Dict[str, Any] = None,\n        headers: Dict[str, Any] = None,\n        auth: Union[Iterable[str], AuthBase] = None,\n        json: Union[List, Dict[str, Any]] = None,\n        timeout: int = 60,\n        credentials: Union[Iterable[str], AuthBase] = None,\n    ) -> None:\n        \"\"\"Creates a new instance of ``APIDataSet`` to fetch data from an API endpoint.\n\n        Args:\n            url: The API URL endpoint.\n            method: The Method of the request, GET, POST, PUT, DELETE, HEAD, etc...\n            data: The request payload, used for POST, PUT, etc requests\n                https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests\n            params: The url parameters of the API.\n                https://requests.readthedocs.io/en/master/user/quickstart/#passing-parameters-in-urls\n            headers: The HTTP headers.\n                https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers\n            auth: Anything ``requests`` accepts. Normally it's either ``('login', 'password')``,\n                or ``AuthBase``, ``HTTPBasicAuth`` instance for more complex cases. Any\n                iterable will be cast to a tuple.\n            json: The request payload, used for POST, PUT, etc requests, passed in\n                to the json kwarg in the requests object.\n                https://requests.readthedocs.io/en/master/user/quickstart/#more-complicated-post-requests\n            timeout: The wait time in seconds for a response, defaults to 1 minute.\n                https://requests.readthedocs.io/en/master/user/quickstart/#timeouts\n            credentials: same as ``auth``. Allows specifying ``auth`` secrets in\n                credentials.yml.\n\n        Raises:\n            ValueError: if both ``credentials`` and ``auth`` are specified.\n        \"\"\"\n        super().__init__()\n\n        if credentials is not None and auth is not None:\n            raise ValueError(\"Cannot specify both auth and credentials.\")\n\n        auth = credentials or auth\n\n        if isinstance(auth, Iterable):\n            auth = tuple(auth)\n\n        self._request_args: Dict[str, Any] = {\n            \"url\": url,\n            \"method\": method,\n            \"data\": data,\n            \"params\": params,\n            \"headers\": headers,\n            \"auth\": auth,\n            \"json\": json,\n            \"timeout\": timeout,\n        }\n\n    def _describe(self) -> Dict[str, Any]:\n        return dict(**self._request_args)\n\n    def _execute_request(self) -> requests.Response:\n        try:\n            response = requests.request(**self._request_args)\n            response.raise_for_status()\n        except requests.exceptions.HTTPError as exc:\n            raise DataSetError(\"Failed to fetch data\", exc) from exc\n        except OSError as exc:\n            raise DataSetError(\"Failed to connect to the remote server\") from exc\n\n        return response\n\n    def _load(self) -> requests.Response:\n        return self._execute_request()\n\n    def _save(self, data: Any) -> None:\n        raise DataSetError(f\"{self.__class__.__name__} is a read only data set type\")\n\n    def _exists(self) -> bool:\n        response = self._execute_request()\n\n        return response.ok\n", "path": "kedro/extras/datasets/api/api_dataset.py"}]}