prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  [BUG] Exchange rate provider is closed now
  ### Please verify that this bug has NOT been raised before.
  
  - [X] I checked and didn't find a similar issue
  
  ### Describe the bug*
  
  Our current default exchange rate provider is closed now - and the change was not smooth either. See https://github.com/Formicka/exchangerate.host/issues/236#issuecomment-1738539199
  
  We need to:
  1) Disable the CI checks for now
  2) Implement an alternative
  3) (Optional) Bring down ApiLayer once and for all before they close all APIs we need
  
  ### Steps to Reproduce
  
  Look at any current [CI](https://github.com/inventree/InvenTree/actions/runs/6333198194/job/17202067465?pr=5582)
  
  ### Expected behaviour
  
  Updating exchange rates
  
  ### Deployment Method
  
  - [X] Docker
  - [X] Bare metal
  
  ### Version Information
  
  Every version>0.6.0 and a couble before are probably effected.
  
  ### Please verify if you can reproduce this bug on the demo site.
  
  - [X] I can reproduce this bug on the demo site.
  
  ### Relevant log output
  
  _No response_
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `InvenTree/InvenTree/exchange.py`
  Content:
  ```
  1 """Exchangerate backend to use `exchangerate.host` to get rates."""
  2 
  3 import ssl
  4 from urllib.error import URLError
  5 from urllib.request import urlopen
  6 
  7 from django.db.utils import OperationalError
  8 
  9 import certifi
  10 from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
  11 
  12 from common.settings import currency_code_default, currency_codes
  13 
  14 
  15 class InvenTreeExchange(SimpleExchangeBackend):
  16     """Backend for automatically updating currency exchange rates.
  17 
  18     Uses the `exchangerate.host` service API
  19     """
  20 
  21     name = "InvenTreeExchange"
  22 
  23     def __init__(self):
  24         """Set API url."""
  25         self.url = "https://api.exchangerate.host/latest"
  26 
  27         super().__init__()
  28 
  29     def get_params(self):
  30         """Placeholder to set API key. Currently not required by `exchangerate.host`."""
  31         # No API key is required
  32         return {
  33         }
  34 
  35     def get_response(self, **kwargs):
  36         """Custom code to get response from server.
  37 
  38         Note: Adds a 5-second timeout
  39         """
  40         url = self.get_url(**kwargs)
  41 
  42         try:
  43             context = ssl.create_default_context(cafile=certifi.where())
  44             response = urlopen(url, timeout=5, context=context)
  45             return response.read()
  46         except Exception:
  47             # Something has gone wrong, but we can just try again next time
  48             # Raise a TypeError so the outer function can handle this
  49             raise TypeError
  50 
  51     def update_rates(self, base_currency=None):
  52         """Set the requested currency codes and get rates."""
  53         # Set default - see B008
  54         if base_currency is None:
  55             base_currency = currency_code_default()
  56 
  57         symbols = ','.join(currency_codes())
  58 
  59         try:
  60             super().update_rates(base=base_currency, symbols=symbols)
  61         # catch connection errors
  62         except URLError:
  63             print('Encountered connection error while updating')
  64         except TypeError:
  65             print('Exchange returned invalid response')
  66         except OperationalError as e:
  67             if 'SerializationFailure' in e.__cause__.__class__.__name__:
  68                 print('Serialization Failure while updating exchange rates')
  69                 # We are just going to swallow this exception because the
  70                 # exchange rates will be updated later by the scheduled task
  71             else:
  72                 # Other operational errors probably are still show stoppers
  73                 # so reraise them so that the log contains the stacktrace
  74                 raise
  75
  ```
  --- 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/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py
  --- a/InvenTree/InvenTree/exchange.py
  +++ b/InvenTree/InvenTree/exchange.py
  @@ -1,12 +1,11 @@
  -"""Exchangerate backend to use `exchangerate.host` to get rates."""
  +"""Exchangerate backend to use `frankfurter.app` to get rates."""
   
  -import ssl
  +from decimal import Decimal
   from urllib.error import URLError
  -from urllib.request import urlopen
   
   from django.db.utils import OperationalError
   
  -import certifi
  +import requests
   from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
   
   from common.settings import currency_code_default, currency_codes
  @@ -15,19 +14,19 @@
   class InvenTreeExchange(SimpleExchangeBackend):
       """Backend for automatically updating currency exchange rates.
   
  -    Uses the `exchangerate.host` service API
  +    Uses the `frankfurter.app` service API
       """
   
       name = "InvenTreeExchange"
   
       def __init__(self):
           """Set API url."""
  -        self.url = "https://api.exchangerate.host/latest"
  +        self.url = "https://api.frankfurter.app/latest"
   
           super().__init__()
   
       def get_params(self):
  -        """Placeholder to set API key. Currently not required by `exchangerate.host`."""
  +        """Placeholder to set API key. Currently not required by `frankfurter.app`."""
           # No API key is required
           return {
           }
  @@ -40,14 +39,22 @@
           url = self.get_url(**kwargs)
   
           try:
  -            context = ssl.create_default_context(cafile=certifi.where())
  -            response = urlopen(url, timeout=5, context=context)
  -            return response.read()
  +            response = requests.get(url=url, timeout=5)
  +            return response.content
           except Exception:
               # Something has gone wrong, but we can just try again next time
               # Raise a TypeError so the outer function can handle this
               raise TypeError
   
  +    def get_rates(self, **params):
  +        """Intersect the requested currency codes with the available codes."""
  +        rates = super().get_rates(**params)
  +
  +        # Add the base currency to the rates
  +        rates[params["base_currency"]] = Decimal("1.0")
  +
  +        return rates
  +
       def update_rates(self, base_currency=None):
           """Set the requested currency codes and get rates."""
           # Set default - see B008
  
verification_info: {"golden_diff": "diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py\n--- a/InvenTree/InvenTree/exchange.py\n+++ b/InvenTree/InvenTree/exchange.py\n@@ -1,12 +1,11 @@\n-\"\"\"Exchangerate backend to use `exchangerate.host` to get rates.\"\"\"\n+\"\"\"Exchangerate backend to use `frankfurter.app` to get rates.\"\"\"\n \n-import ssl\n+from decimal import Decimal\n from urllib.error import URLError\n-from urllib.request import urlopen\n \n from django.db.utils import OperationalError\n \n-import certifi\n+import requests\n from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend\n \n from common.settings import currency_code_default, currency_codes\n@@ -15,19 +14,19 @@\n class InvenTreeExchange(SimpleExchangeBackend):\n     \"\"\"Backend for automatically updating currency exchange rates.\n \n-    Uses the `exchangerate.host` service API\n+    Uses the `frankfurter.app` service API\n     \"\"\"\n \n     name = \"InvenTreeExchange\"\n \n     def __init__(self):\n         \"\"\"Set API url.\"\"\"\n-        self.url = \"https://api.exchangerate.host/latest\"\n+        self.url = \"https://api.frankfurter.app/latest\"\n \n         super().__init__()\n \n     def get_params(self):\n-        \"\"\"Placeholder to set API key. Currently not required by `exchangerate.host`.\"\"\"\n+        \"\"\"Placeholder to set API key. Currently not required by `frankfurter.app`.\"\"\"\n         # No API key is required\n         return {\n         }\n@@ -40,14 +39,22 @@\n         url = self.get_url(**kwargs)\n \n         try:\n-            context = ssl.create_default_context(cafile=certifi.where())\n-            response = urlopen(url, timeout=5, context=context)\n-            return response.read()\n+            response = requests.get(url=url, timeout=5)\n+            return response.content\n         except Exception:\n             # Something has gone wrong, but we can just try again next time\n             # Raise a TypeError so the outer function can handle this\n             raise TypeError\n \n+    def get_rates(self, **params):\n+        \"\"\"Intersect the requested currency codes with the available codes.\"\"\"\n+        rates = super().get_rates(**params)\n+\n+        # Add the base currency to the rates\n+        rates[params[\"base_currency\"]] = Decimal(\"1.0\")\n+\n+        return rates\n+\n     def update_rates(self, base_currency=None):\n         \"\"\"Set the requested currency codes and get rates.\"\"\"\n         # Set default - see B008\n", "issue": "[BUG] Exchange rate provider is closed now\n### Please verify that this bug has NOT been raised before.\n\n- [X] I checked and didn't find a similar issue\n\n### Describe the bug*\n\nOur current default exchange rate provider is closed now - and the change was not smooth either. See https://github.com/Formicka/exchangerate.host/issues/236#issuecomment-1738539199\r\n\r\nWe need to:\r\n1) Disable the CI checks for now\r\n2) Implement an alternative\r\n3) (Optional) Bring down ApiLayer once and for all before they close all APIs we need\n\n### Steps to Reproduce\n\nLook at any current [CI](https://github.com/inventree/InvenTree/actions/runs/6333198194/job/17202067465?pr=5582)\n\n### Expected behaviour\n\nUpdating exchange rates\n\n### Deployment Method\n\n- [X] Docker\n- [X] Bare metal\n\n### Version Information\n\nEvery version>0.6.0 and a couble before are probably effected.\n\n### Please verify if you can reproduce this bug on the demo site.\n\n- [X] I can reproduce this bug on the demo site.\n\n### Relevant log output\n\n_No response_\n", "before_files": [{"content": "\"\"\"Exchangerate backend to use `exchangerate.host` to get rates.\"\"\"\n\nimport ssl\nfrom urllib.error import URLError\nfrom urllib.request import urlopen\n\nfrom django.db.utils import OperationalError\n\nimport certifi\nfrom djmoney.contrib.exchange.backends.base import SimpleExchangeBackend\n\nfrom common.settings import currency_code_default, currency_codes\n\n\nclass InvenTreeExchange(SimpleExchangeBackend):\n    \"\"\"Backend for automatically updating currency exchange rates.\n\n    Uses the `exchangerate.host` service API\n    \"\"\"\n\n    name = \"InvenTreeExchange\"\n\n    def __init__(self):\n        \"\"\"Set API url.\"\"\"\n        self.url = \"https://api.exchangerate.host/latest\"\n\n        super().__init__()\n\n    def get_params(self):\n        \"\"\"Placeholder to set API key. Currently not required by `exchangerate.host`.\"\"\"\n        # No API key is required\n        return {\n        }\n\n    def get_response(self, **kwargs):\n        \"\"\"Custom code to get response from server.\n\n        Note: Adds a 5-second timeout\n        \"\"\"\n        url = self.get_url(**kwargs)\n\n        try:\n            context = ssl.create_default_context(cafile=certifi.where())\n            response = urlopen(url, timeout=5, context=context)\n            return response.read()\n        except Exception:\n            # Something has gone wrong, but we can just try again next time\n            # Raise a TypeError so the outer function can handle this\n            raise TypeError\n\n    def update_rates(self, base_currency=None):\n        \"\"\"Set the requested currency codes and get rates.\"\"\"\n        # Set default - see B008\n        if base_currency is None:\n            base_currency = currency_code_default()\n\n        symbols = ','.join(currency_codes())\n\n        try:\n            super().update_rates(base=base_currency, symbols=symbols)\n        # catch connection errors\n        except URLError:\n            print('Encountered connection error while updating')\n        except TypeError:\n            print('Exchange returned invalid response')\n        except OperationalError as e:\n            if 'SerializationFailure' in e.__cause__.__class__.__name__:\n                print('Serialization Failure while updating exchange rates')\n                # We are just going to swallow this exception because the\n                # exchange rates will be updated later by the scheduled task\n            else:\n                # Other operational errors probably are still show stoppers\n                # so reraise them so that the log contains the stacktrace\n                raise\n", "path": "InvenTree/InvenTree/exchange.py"}], "after_files": [{"content": "\"\"\"Exchangerate backend to use `frankfurter.app` to get rates.\"\"\"\n\nfrom decimal import Decimal\nfrom urllib.error import URLError\n\nfrom django.db.utils import OperationalError\n\nimport requests\nfrom djmoney.contrib.exchange.backends.base import SimpleExchangeBackend\n\nfrom common.settings import currency_code_default, currency_codes\n\n\nclass InvenTreeExchange(SimpleExchangeBackend):\n    \"\"\"Backend for automatically updating currency exchange rates.\n\n    Uses the `frankfurter.app` service API\n    \"\"\"\n\n    name = \"InvenTreeExchange\"\n\n    def __init__(self):\n        \"\"\"Set API url.\"\"\"\n        self.url = \"https://api.frankfurter.app/latest\"\n\n        super().__init__()\n\n    def get_params(self):\n        \"\"\"Placeholder to set API key. Currently not required by `frankfurter.app`.\"\"\"\n        # No API key is required\n        return {\n        }\n\n    def get_response(self, **kwargs):\n        \"\"\"Custom code to get response from server.\n\n        Note: Adds a 5-second timeout\n        \"\"\"\n        url = self.get_url(**kwargs)\n\n        try:\n            response = requests.get(url=url, timeout=5)\n            return response.content\n        except Exception:\n            # Something has gone wrong, but we can just try again next time\n            # Raise a TypeError so the outer function can handle this\n            raise TypeError\n\n    def get_rates(self, **params):\n        \"\"\"Intersect the requested currency codes with the available codes.\"\"\"\n        rates = super().get_rates(**params)\n\n        # Add the base currency to the rates\n        rates[params[\"base_currency\"]] = Decimal(\"1.0\")\n\n        return rates\n\n    def update_rates(self, base_currency=None):\n        \"\"\"Set the requested currency codes and get rates.\"\"\"\n        # Set default - see B008\n        if base_currency is None:\n            base_currency = currency_code_default()\n\n        symbols = ','.join(currency_codes())\n\n        try:\n            super().update_rates(base=base_currency, symbols=symbols)\n        # catch connection errors\n        except URLError:\n            print('Encountered connection error while updating')\n        except TypeError:\n            print('Exchange returned invalid response')\n        except OperationalError as e:\n            if 'SerializationFailure' in e.__cause__.__class__.__name__:\n                print('Serialization Failure while updating exchange rates')\n                # We are just going to swallow this exception because the\n                # exchange rates will be updated later by the scheduled task\n            else:\n                # Other operational errors probably are still show stoppers\n                # so reraise them so that the log contains the stacktrace\n                raise\n", "path": "InvenTree/InvenTree/exchange.py"}]}