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()

```

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

{"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"}]}