prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Client: API token saving bug on MacOS
  @banesullivan was experiencing the following issue:
  
  When calling `create_rgd_client`, if there isn't already a token stored locally (in `$HOME/.rgd/token`), the client is supposed to make a request to the server to create it, and save it in that file. On MacOS, it seems this might not be occurring. The file doesn't appear to ever be created (notably though, the `.rgd` folder _is_ present). Furthermore, if you try to manually populate that file with your token, it will correctly read it, but the file will then be gone afterwards.
  
  This doesn't actually affect authorization, as it still just fetches the token from the API and stores it in memory, but the storage issue needs to be looked into.
  
  </issue>
  <code>
  [start of django-rgd/client/rgd_client/client.py]
  1 import getpass
  2 import os
  3 from typing import List, Optional, Type
  4 
  5 import requests
  6 
  7 from .plugin import CorePlugin
  8 from .session import RgdClientSession, clone_session
  9 from .utils import API_KEY_DIR_PATH, API_KEY_FILE_NAME, DEFAULT_RGD_API
  10 
  11 
  12 class RgdClient:
  13     def __init__(
  14         self,
  15         api_url: str = DEFAULT_RGD_API,
  16         username: Optional[str] = None,
  17         password: Optional[str] = None,
  18         save: Optional[bool] = True,
  19     ) -> None:
  20         """
  21         Initialize the base RGD Client.
  22 
  23         Args:
  24             api_url: The base url of the RGD API instance.
  25             username: The username to authenticate to the instance with, if any.
  26             password: The password associated with the provided username. If None, a prompt will be provided.
  27             save: Whether or not to save the logged-in user's API key to disk for future use.
  28 
  29         Returns:
  30             A base RgdClient instance.
  31         """
  32         # Look for an API key in the environment. If it's not there, check username/password
  33         api_key = _read_api_key()
  34         if api_key is None:
  35             if username is not None and password is None:
  36                 password = getpass.getpass()
  37 
  38             # Get an API key for this user and save it to disk
  39             if username and password:
  40                 api_key = _get_api_key(api_url, username, password, save)
  41 
  42         auth_header = f'Token {api_key}'
  43 
  44         self.session = RgdClientSession(base_url=api_url, auth_header=auth_header)
  45         self.rgd = CorePlugin(clone_session(self.session))
  46 
  47     def clear_token(self):
  48         """Delete a locally-stored API key."""
  49         (API_KEY_DIR_PATH / API_KEY_FILE_NAME).unlink(missing_ok=True)
  50 
  51 
  52 def _get_api_key(api_url: str, username: str, password: str, save: bool) -> str:
  53     """Get an RGD API Key for the given user from the server, and save it if requested."""
  54     resp = requests.post(f'{api_url}/api-token-auth', {'username': username, 'password': password})
  55     resp.raise_for_status()
  56     token = resp.json()['token']
  57     if save:
  58         API_KEY_DIR_PATH.mkdir(parents=True, exist_ok=True)
  59         with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'w') as fd:
  60             fd.write(token)
  61     return token
  62 
  63 
  64 def _read_api_key() -> Optional[str]:
  65     """
  66     Retrieve an RGD API Key from the users environment.
  67 
  68     This function checks for an environment variable named RGD_API_TOKEN and returns it if it exists.
  69     If it does not exist, it looks for a file located at ~/.rgd/token and returns its contents.
  70     """
  71     token = os.getenv('RGD_API_TOKEN', None)
  72     if token is not None:
  73         return token
  74 
  75     try:
  76         # read the first line of the text file at ~/.rgd/token
  77         with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'r') as fd:
  78             return fd.readline().strip()
  79     except FileNotFoundError:
  80         return None
  81 
  82 
  83 def create_rgd_client(
  84     api_url: str = DEFAULT_RGD_API,
  85     username: Optional[str] = None,
  86     password: Optional[str] = None,
  87     save: Optional[bool] = True,
  88     extra_plugins: Optional[List[Type]] = None,
  89 ):
  90     # Avoid circular import
  91     from ._plugin_utils import _inject_plugin_deps, _plugin_classes, _plugin_instances
  92 
  93     # Create initial client
  94     client = RgdClient(api_url, username, password, save)
  95 
  96     # Perform plugin initialization
  97     plugin_classes = _plugin_classes(extra_plugins=extra_plugins)
  98     plugin_instances = _plugin_instances(client, plugin_classes)
  99     _inject_plugin_deps(plugin_instances)
  100 
  101     return client
  102
  [end of django-rgd/client/rgd_client/client.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/django-rgd/client/rgd_client/client.py b/django-rgd/client/rgd_client/client.py
  --- a/django-rgd/client/rgd_client/client.py
  +++ b/django-rgd/client/rgd_client/client.py
  @@ -1,4 +1,5 @@
   import getpass
  +import logging
   import os
   from typing import List, Optional, Type
   
  @@ -8,6 +9,8 @@
   from .session import RgdClientSession, clone_session
   from .utils import API_KEY_DIR_PATH, API_KEY_FILE_NAME, DEFAULT_RGD_API
   
  +logger = logging.getLogger(__name__)
  +
   
   class RgdClient:
       def __init__(
  @@ -30,7 +33,7 @@
               A base RgdClient instance.
           """
           # Look for an API key in the environment. If it's not there, check username/password
  -        api_key = _read_api_key()
  +        api_key = _read_api_key(api_url=api_url, username=username, password=password)
           if api_key is None:
               if username is not None and password is None:
                   password = getpass.getpass()
  @@ -38,6 +41,10 @@
               # Get an API key for this user and save it to disk
               if username and password:
                   api_key = _get_api_key(api_url, username, password, save)
  +                if api_key is None:
  +                    logger.error(
  +                        'Failed to retrieve API key; are your username and password correct?'
  +                    )
   
           auth_header = f'Token {api_key}'
   
  @@ -49,11 +56,12 @@
           (API_KEY_DIR_PATH / API_KEY_FILE_NAME).unlink(missing_ok=True)
   
   
  -def _get_api_key(api_url: str, username: str, password: str, save: bool) -> str:
  +def _get_api_key(api_url: str, username: str, password: str, save: bool) -> Optional[str]:
       """Get an RGD API Key for the given user from the server, and save it if requested."""
       resp = requests.post(f'{api_url}/api-token-auth', {'username': username, 'password': password})
  -    resp.raise_for_status()
  -    token = resp.json()['token']
  +    token = resp.json().get('token')
  +    if token is None:
  +        return None
       if save:
           API_KEY_DIR_PATH.mkdir(parents=True, exist_ok=True)
           with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'w') as fd:
  @@ -61,7 +69,7 @@
       return token
   
   
  -def _read_api_key() -> Optional[str]:
  +def _read_api_key(api_url: str, username: str = None, password: str = None) -> Optional[str]:
       """
       Retrieve an RGD API Key from the users environment.
   
  @@ -75,10 +83,29 @@
       try:
           # read the first line of the text file at ~/.rgd/token
           with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'r') as fd:
  -            return fd.readline().strip()
  +            api_key = fd.readline().strip()
       except FileNotFoundError:
           return None
   
  +    # Make sure API key works by hitting a protected endpoint
  +    resp = requests.get(f'{api_url}/rgd/collection', headers={'Authorization': f'Token {api_key}'})
  +
  +    # If it doesn't, try to get a new one and save it to ~/.rgd/token, as the current one is corrupted
  +    if resp.status_code == 401:
  +        logger.error('API key is invalid.')
  +        # If username + password were provided, try to get a new API key with them
  +        if username is not None and password is not None:
  +            logger.warning('Attempting to fetch a new API key...')
  +            api_key = _get_api_key(api_url, username, password, save=True)
  +            if api_key is not None:
  +                logger.warning('Succeeded.')
  +            return api_key
  +        else:
  +            logger.error('Provide your username and password next time to fetch a new one.')
  +            return None
  +
  +    return api_key
  +
   
   def create_rgd_client(
       api_url: str = DEFAULT_RGD_API,
  
verification_info: {"golden_diff": "diff --git a/django-rgd/client/rgd_client/client.py b/django-rgd/client/rgd_client/client.py\n--- a/django-rgd/client/rgd_client/client.py\n+++ b/django-rgd/client/rgd_client/client.py\n@@ -1,4 +1,5 @@\n import getpass\n+import logging\n import os\n from typing import List, Optional, Type\n \n@@ -8,6 +9,8 @@\n from .session import RgdClientSession, clone_session\n from .utils import API_KEY_DIR_PATH, API_KEY_FILE_NAME, DEFAULT_RGD_API\n \n+logger = logging.getLogger(__name__)\n+\n \n class RgdClient:\n     def __init__(\n@@ -30,7 +33,7 @@\n             A base RgdClient instance.\n         \"\"\"\n         # Look for an API key in the environment. If it's not there, check username/password\n-        api_key = _read_api_key()\n+        api_key = _read_api_key(api_url=api_url, username=username, password=password)\n         if api_key is None:\n             if username is not None and password is None:\n                 password = getpass.getpass()\n@@ -38,6 +41,10 @@\n             # Get an API key for this user and save it to disk\n             if username and password:\n                 api_key = _get_api_key(api_url, username, password, save)\n+                if api_key is None:\n+                    logger.error(\n+                        'Failed to retrieve API key; are your username and password correct?'\n+                    )\n \n         auth_header = f'Token {api_key}'\n \n@@ -49,11 +56,12 @@\n         (API_KEY_DIR_PATH / API_KEY_FILE_NAME).unlink(missing_ok=True)\n \n \n-def _get_api_key(api_url: str, username: str, password: str, save: bool) -> str:\n+def _get_api_key(api_url: str, username: str, password: str, save: bool) -> Optional[str]:\n     \"\"\"Get an RGD API Key for the given user from the server, and save it if requested.\"\"\"\n     resp = requests.post(f'{api_url}/api-token-auth', {'username': username, 'password': password})\n-    resp.raise_for_status()\n-    token = resp.json()['token']\n+    token = resp.json().get('token')\n+    if token is None:\n+        return None\n     if save:\n         API_KEY_DIR_PATH.mkdir(parents=True, exist_ok=True)\n         with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'w') as fd:\n@@ -61,7 +69,7 @@\n     return token\n \n \n-def _read_api_key() -> Optional[str]:\n+def _read_api_key(api_url: str, username: str = None, password: str = None) -> Optional[str]:\n     \"\"\"\n     Retrieve an RGD API Key from the users environment.\n \n@@ -75,10 +83,29 @@\n     try:\n         # read the first line of the text file at ~/.rgd/token\n         with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'r') as fd:\n-            return fd.readline().strip()\n+            api_key = fd.readline().strip()\n     except FileNotFoundError:\n         return None\n \n+    # Make sure API key works by hitting a protected endpoint\n+    resp = requests.get(f'{api_url}/rgd/collection', headers={'Authorization': f'Token {api_key}'})\n+\n+    # If it doesn't, try to get a new one and save it to ~/.rgd/token, as the current one is corrupted\n+    if resp.status_code == 401:\n+        logger.error('API key is invalid.')\n+        # If username + password were provided, try to get a new API key with them\n+        if username is not None and password is not None:\n+            logger.warning('Attempting to fetch a new API key...')\n+            api_key = _get_api_key(api_url, username, password, save=True)\n+            if api_key is not None:\n+                logger.warning('Succeeded.')\n+            return api_key\n+        else:\n+            logger.error('Provide your username and password next time to fetch a new one.')\n+            return None\n+\n+    return api_key\n+\n \n def create_rgd_client(\n     api_url: str = DEFAULT_RGD_API,\n", "issue": "Client: API token saving bug on MacOS\n@banesullivan was experiencing the following issue:\r\n\r\nWhen calling `create_rgd_client`, if there isn't already a token stored locally (in `$HOME/.rgd/token`), the client is supposed to make a request to the server to create it, and save it in that file. On MacOS, it seems this might not be occurring. The file doesn't appear to ever be created (notably though, the `.rgd` folder _is_ present). Furthermore, if you try to manually populate that file with your token, it will correctly read it, but the file will then be gone afterwards.\r\n\r\nThis doesn't actually affect authorization, as it still just fetches the token from the API and stores it in memory, but the storage issue needs to be looked into.\n", "before_files": [{"content": "import getpass\nimport os\nfrom typing import List, Optional, Type\n\nimport requests\n\nfrom .plugin import CorePlugin\nfrom .session import RgdClientSession, clone_session\nfrom .utils import API_KEY_DIR_PATH, API_KEY_FILE_NAME, DEFAULT_RGD_API\n\n\nclass RgdClient:\n    def __init__(\n        self,\n        api_url: str = DEFAULT_RGD_API,\n        username: Optional[str] = None,\n        password: Optional[str] = None,\n        save: Optional[bool] = True,\n    ) -> None:\n        \"\"\"\n        Initialize the base RGD Client.\n\n        Args:\n            api_url: The base url of the RGD API instance.\n            username: The username to authenticate to the instance with, if any.\n            password: The password associated with the provided username. If None, a prompt will be provided.\n            save: Whether or not to save the logged-in user's API key to disk for future use.\n\n        Returns:\n            A base RgdClient instance.\n        \"\"\"\n        # Look for an API key in the environment. If it's not there, check username/password\n        api_key = _read_api_key()\n        if api_key is None:\n            if username is not None and password is None:\n                password = getpass.getpass()\n\n            # Get an API key for this user and save it to disk\n            if username and password:\n                api_key = _get_api_key(api_url, username, password, save)\n\n        auth_header = f'Token {api_key}'\n\n        self.session = RgdClientSession(base_url=api_url, auth_header=auth_header)\n        self.rgd = CorePlugin(clone_session(self.session))\n\n    def clear_token(self):\n        \"\"\"Delete a locally-stored API key.\"\"\"\n        (API_KEY_DIR_PATH / API_KEY_FILE_NAME).unlink(missing_ok=True)\n\n\ndef _get_api_key(api_url: str, username: str, password: str, save: bool) -> str:\n    \"\"\"Get an RGD API Key for the given user from the server, and save it if requested.\"\"\"\n    resp = requests.post(f'{api_url}/api-token-auth', {'username': username, 'password': password})\n    resp.raise_for_status()\n    token = resp.json()['token']\n    if save:\n        API_KEY_DIR_PATH.mkdir(parents=True, exist_ok=True)\n        with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'w') as fd:\n            fd.write(token)\n    return token\n\n\ndef _read_api_key() -> Optional[str]:\n    \"\"\"\n    Retrieve an RGD API Key from the users environment.\n\n    This function checks for an environment variable named RGD_API_TOKEN and returns it if it exists.\n    If it does not exist, it looks for a file located at ~/.rgd/token and returns its contents.\n    \"\"\"\n    token = os.getenv('RGD_API_TOKEN', None)\n    if token is not None:\n        return token\n\n    try:\n        # read the first line of the text file at ~/.rgd/token\n        with open(API_KEY_DIR_PATH / API_KEY_FILE_NAME, 'r') as fd:\n            return fd.readline().strip()\n    except FileNotFoundError:\n        return None\n\n\ndef create_rgd_client(\n    api_url: str = DEFAULT_RGD_API,\n    username: Optional[str] = None,\n    password: Optional[str] = None,\n    save: Optional[bool] = True,\n    extra_plugins: Optional[List[Type]] = None,\n):\n    # Avoid circular import\n    from ._plugin_utils import _inject_plugin_deps, _plugin_classes, _plugin_instances\n\n    # Create initial client\n    client = RgdClient(api_url, username, password, save)\n\n    # Perform plugin initialization\n    plugin_classes = _plugin_classes(extra_plugins=extra_plugins)\n    plugin_instances = _plugin_instances(client, plugin_classes)\n    _inject_plugin_deps(plugin_instances)\n\n    return client\n", "path": "django-rgd/client/rgd_client/client.py"}]}