prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Sentry ratelimit cannot be changed when using self-hosted
  ## Important Details
  
  How are you running Sentry?
  
  <!-- Please pick one of the following -->
  On-Premise wo/ Docker, version 20.8.0
  
  ## Description
  Sentry ratelimit cannot be changed when running on premises
  
  ## Steps to Reproduce
  
  1. Go to web-interface, Admin/Settings
  2. Set a non-zero ratelimit.
  3. Get an error.
  
  ````
  Oct  6 07:18:49 jsentry sentry[4128]: 10.100.33.5 - - [06/Oct/2020:04:18:49 +0000] "GET /api/0/internal/options/ HTTP/1.1" 200 20407 "https://sentry.findmykids.org/manage/settings/" "Mozilla/5.0 (X11; FreeBSD amd64; rv:76.0) Gecko/20100101 Firefox/76.0"
  Oct  6 07:19:09 jsentry sentry[4128]: Traceback (most recent call last):
  Oct  6 07:19:09 jsentry sentry[4128]:   File "/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/base.py", line 134, in handle_exception
  Oct  6 07:19:09 jsentry sentry[4128]:     response = super(Endpoint, self).handle_exception(exc)
  Oct  6 07:19:09 jsentry sentry[4128]:   File "/usr/local/lib/python2.7/site-packages/djangorestframework-3.6.4-py2.7.egg/rest_framework/views.py", line 449, in handle_exception
  Oct  6 07:19:09 jsentry sentry[4128]:     self.raise_uncaught_exception(exc)
  Oct  6 07:19:09 jsentry sentry[4128]:   File "/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/base.py", line 247, in dispatch
  Oct  6 07:19:09 jsentry sentry[4128]:     response = handler(request, *args, **kwargs)
  Oct  6 07:19:09 jsentry sentry[4128]:   File "/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/endpoints/system_options.py", line 74, in put
  Oct  6 07:19:09 jsentry sentry[4128]:     options.set(k, v)
  Oct  6 07:19:09 jsentry sentry[4128]:   File "/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/options/manager.py", line 83, in set
  Oct  6 07:19:09 jsentry sentry[4128]:     "%r cannot be changed at runtime because it is configured on disk" % key
  Oct  6 07:19:09 jsentry sentry[4128]: AssertionError: u'system.url-prefix' cannot be changed at runtime because it is configured on disk
  Oct  6 07:19:09 jsentry sentry[4128]: 10.100.33.5 - - [06/Oct/2020:04:19:09 +0000] "PUT /api/0/internal/options/ HTTP/1.1" 500 746 "https://sentry.findmykids.org/manage/settings/" "Mozilla/5.0 (X11; FreeBSD amd64; rv:76.0) Gecko/20100101 Firefox/76.0"
  ````
  
  ### What you expected to happen
  Ratelimit should be changeable from web-interface.
  
  ### Possible Solution
  Stop sending system.url-prefix in PUT request ?
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/sentry/api/endpoints/system_options.py`
  Content:
  ```
  1 from __future__ import absolute_import
  2 
  3 import six
  4 
  5 import sentry
  6 
  7 from django.conf import settings
  8 from rest_framework.response import Response
  9 
  10 from sentry import options
  11 from sentry.api.base import Endpoint
  12 from sentry.api.permissions import SuperuserPermission
  13 from sentry.utils.email import is_smtp_enabled
  14 
  15 
  16 class SystemOptionsEndpoint(Endpoint):
  17     permission_classes = (SuperuserPermission,)
  18 
  19     def get(self, request):
  20         query = request.GET.get("query")
  21         if query == "is:required":
  22             option_list = options.filter(flag=options.FLAG_REQUIRED)
  23         elif query:
  24             return Response(u"{} is not a supported search query".format(query), status=400)
  25         else:
  26             option_list = options.all()
  27 
  28         smtp_disabled = not is_smtp_enabled()
  29 
  30         results = {}
  31         for k in option_list:
  32             disabled, disabled_reason = False, None
  33 
  34             if smtp_disabled and k.name[:5] == "mail.":
  35                 disabled_reason, disabled = "smtpDisabled", True
  36             elif bool(
  37                 k.flags & options.FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(k.name)
  38             ):
  39                 # TODO(mattrobenolt): Expose this as a property on Key.
  40                 disabled_reason, disabled = "diskPriority", True
  41 
  42             # TODO(mattrobenolt): help, placeholder, title, type
  43             results[k.name] = {
  44                 "value": options.get(k.name),
  45                 "field": {
  46                     "default": k.default(),
  47                     "required": bool(k.flags & options.FLAG_REQUIRED),
  48                     "disabled": disabled,
  49                     "disabledReason": disabled_reason,
  50                     "isSet": options.isset(k.name),
  51                     "allowEmpty": bool(k.flags & options.FLAG_ALLOW_EMPTY),
  52                 },
  53             }
  54 
  55         return Response(results)
  56 
  57     def put(self, request):
  58         # TODO(dcramer): this should validate options before saving them
  59         for k, v in six.iteritems(request.data):
  60             if v and isinstance(v, six.string_types):
  61                 v = v.strip()
  62             try:
  63                 option = options.lookup_key(k)
  64             except options.UnknownOption:
  65                 # TODO(dcramer): unify API errors
  66                 return Response(
  67                     {"error": "unknown_option", "errorDetail": {"option": k}}, status=400
  68                 )
  69 
  70             try:
  71                 if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v:
  72                     options.delete(k)
  73                 else:
  74                     options.set(k, v)
  75             except TypeError as e:
  76                 return Response(
  77                     {
  78                         "error": "invalid_type",
  79                         "errorDetail": {"option": k, "message": six.text_type(e)},
  80                     },
  81                     status=400,
  82                 )
  83         # TODO(dcramer): this has nothing to do with configuring options and
  84         # should not be set here
  85         options.set("sentry:version-configured", sentry.get_version())
  86         return Response(status=200)
  87
  ```
  --- 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/src/sentry/api/endpoints/system_options.py b/src/sentry/api/endpoints/system_options.py
  --- a/src/sentry/api/endpoints/system_options.py
  +++ b/src/sentry/api/endpoints/system_options.py
  @@ -72,10 +72,13 @@
                       options.delete(k)
                   else:
                       options.set(k, v)
  -            except TypeError as e:
  +            except (TypeError, AssertionError) as e:
  +                # TODO(chadwhitacre): Use a custom exception for the
  +                # immutability case, especially since asserts disappear with
  +                # `python -O`.
                   return Response(
                       {
  -                        "error": "invalid_type",
  +                        "error": "invalid_type" if type(e) is TypeError else "immutable_option",
                           "errorDetail": {"option": k, "message": six.text_type(e)},
                       },
                       status=400,
  
verification_info: {"golden_diff": "diff --git a/src/sentry/api/endpoints/system_options.py b/src/sentry/api/endpoints/system_options.py\n--- a/src/sentry/api/endpoints/system_options.py\n+++ b/src/sentry/api/endpoints/system_options.py\n@@ -72,10 +72,13 @@\n                     options.delete(k)\n                 else:\n                     options.set(k, v)\n-            except TypeError as e:\n+            except (TypeError, AssertionError) as e:\n+                # TODO(chadwhitacre): Use a custom exception for the\n+                # immutability case, especially since asserts disappear with\n+                # `python -O`.\n                 return Response(\n                     {\n-                        \"error\": \"invalid_type\",\n+                        \"error\": \"invalid_type\" if type(e) is TypeError else \"immutable_option\",\n                         \"errorDetail\": {\"option\": k, \"message\": six.text_type(e)},\n                     },\n                     status=400,\n", "issue": "Sentry ratelimit cannot be changed when using self-hosted\n## Important Details\r\n\r\nHow are you running Sentry?\r\n\r\n<!-- Please pick one of the following -->\r\nOn-Premise wo/ Docker, version 20.8.0\r\n\r\n## Description\r\nSentry ratelimit cannot be changed when running on premises\r\n\r\n## Steps to Reproduce\r\n\r\n1. Go to web-interface, Admin/Settings\r\n2. Set a non-zero ratelimit.\r\n3. Get an error.\r\n\r\n````\r\nOct  6 07:18:49 jsentry sentry[4128]: 10.100.33.5 - - [06/Oct/2020:04:18:49 +0000] \"GET /api/0/internal/options/ HTTP/1.1\" 200 20407 \"https://sentry.findmykids.org/manage/settings/\" \"Mozilla/5.0 (X11; FreeBSD amd64; rv:76.0) Gecko/20100101 Firefox/76.0\"\r\nOct  6 07:19:09 jsentry sentry[4128]: Traceback (most recent call last):\r\nOct  6 07:19:09 jsentry sentry[4128]:   File \"/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/base.py\", line 134, in handle_exception\r\nOct  6 07:19:09 jsentry sentry[4128]:     response = super(Endpoint, self).handle_exception(exc)\r\nOct  6 07:19:09 jsentry sentry[4128]:   File \"/usr/local/lib/python2.7/site-packages/djangorestframework-3.6.4-py2.7.egg/rest_framework/views.py\", line 449, in handle_exception\r\nOct  6 07:19:09 jsentry sentry[4128]:     self.raise_uncaught_exception(exc)\r\nOct  6 07:19:09 jsentry sentry[4128]:   File \"/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/base.py\", line 247, in dispatch\r\nOct  6 07:19:09 jsentry sentry[4128]:     response = handler(request, *args, **kwargs)\r\nOct  6 07:19:09 jsentry sentry[4128]:   File \"/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/api/endpoints/system_options.py\", line 74, in put\r\nOct  6 07:19:09 jsentry sentry[4128]:     options.set(k, v)\r\nOct  6 07:19:09 jsentry sentry[4128]:   File \"/usr/local/lib/python2.7/site-packages/sentry-20.8.0-py2.7.egg/sentry/options/manager.py\", line 83, in set\r\nOct  6 07:19:09 jsentry sentry[4128]:     \"%r cannot be changed at runtime because it is configured on disk\" % key\r\nOct  6 07:19:09 jsentry sentry[4128]: AssertionError: u'system.url-prefix' cannot be changed at runtime because it is configured on disk\r\nOct  6 07:19:09 jsentry sentry[4128]: 10.100.33.5 - - [06/Oct/2020:04:19:09 +0000] \"PUT /api/0/internal/options/ HTTP/1.1\" 500 746 \"https://sentry.findmykids.org/manage/settings/\" \"Mozilla/5.0 (X11; FreeBSD amd64; rv:76.0) Gecko/20100101 Firefox/76.0\"\r\n````\r\n\r\n### What you expected to happen\r\nRatelimit should be changeable from web-interface.\r\n\r\n### Possible Solution\r\nStop sending system.url-prefix in PUT request ?\r\n\n", "before_files": [{"content": "from __future__ import absolute_import\n\nimport six\n\nimport sentry\n\nfrom django.conf import settings\nfrom rest_framework.response import Response\n\nfrom sentry import options\nfrom sentry.api.base import Endpoint\nfrom sentry.api.permissions import SuperuserPermission\nfrom sentry.utils.email import is_smtp_enabled\n\n\nclass SystemOptionsEndpoint(Endpoint):\n    permission_classes = (SuperuserPermission,)\n\n    def get(self, request):\n        query = request.GET.get(\"query\")\n        if query == \"is:required\":\n            option_list = options.filter(flag=options.FLAG_REQUIRED)\n        elif query:\n            return Response(u\"{} is not a supported search query\".format(query), status=400)\n        else:\n            option_list = options.all()\n\n        smtp_disabled = not is_smtp_enabled()\n\n        results = {}\n        for k in option_list:\n            disabled, disabled_reason = False, None\n\n            if smtp_disabled and k.name[:5] == \"mail.\":\n                disabled_reason, disabled = \"smtpDisabled\", True\n            elif bool(\n                k.flags & options.FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(k.name)\n            ):\n                # TODO(mattrobenolt): Expose this as a property on Key.\n                disabled_reason, disabled = \"diskPriority\", True\n\n            # TODO(mattrobenolt): help, placeholder, title, type\n            results[k.name] = {\n                \"value\": options.get(k.name),\n                \"field\": {\n                    \"default\": k.default(),\n                    \"required\": bool(k.flags & options.FLAG_REQUIRED),\n                    \"disabled\": disabled,\n                    \"disabledReason\": disabled_reason,\n                    \"isSet\": options.isset(k.name),\n                    \"allowEmpty\": bool(k.flags & options.FLAG_ALLOW_EMPTY),\n                },\n            }\n\n        return Response(results)\n\n    def put(self, request):\n        # TODO(dcramer): this should validate options before saving them\n        for k, v in six.iteritems(request.data):\n            if v and isinstance(v, six.string_types):\n                v = v.strip()\n            try:\n                option = options.lookup_key(k)\n            except options.UnknownOption:\n                # TODO(dcramer): unify API errors\n                return Response(\n                    {\"error\": \"unknown_option\", \"errorDetail\": {\"option\": k}}, status=400\n                )\n\n            try:\n                if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v:\n                    options.delete(k)\n                else:\n                    options.set(k, v)\n            except TypeError as e:\n                return Response(\n                    {\n                        \"error\": \"invalid_type\",\n                        \"errorDetail\": {\"option\": k, \"message\": six.text_type(e)},\n                    },\n                    status=400,\n                )\n        # TODO(dcramer): this has nothing to do with configuring options and\n        # should not be set here\n        options.set(\"sentry:version-configured\", sentry.get_version())\n        return Response(status=200)\n", "path": "src/sentry/api/endpoints/system_options.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nimport six\n\nimport sentry\n\nfrom django.conf import settings\nfrom rest_framework.response import Response\n\nfrom sentry import options\nfrom sentry.api.base import Endpoint\nfrom sentry.api.permissions import SuperuserPermission\nfrom sentry.utils.email import is_smtp_enabled\n\n\nclass SystemOptionsEndpoint(Endpoint):\n    permission_classes = (SuperuserPermission,)\n\n    def get(self, request):\n        query = request.GET.get(\"query\")\n        if query == \"is:required\":\n            option_list = options.filter(flag=options.FLAG_REQUIRED)\n        elif query:\n            return Response(u\"{} is not a supported search query\".format(query), status=400)\n        else:\n            option_list = options.all()\n\n        smtp_disabled = not is_smtp_enabled()\n\n        results = {}\n        for k in option_list:\n            disabled, disabled_reason = False, None\n\n            if smtp_disabled and k.name[:5] == \"mail.\":\n                disabled_reason, disabled = \"smtpDisabled\", True\n            elif bool(\n                k.flags & options.FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(k.name)\n            ):\n                # TODO(mattrobenolt): Expose this as a property on Key.\n                disabled_reason, disabled = \"diskPriority\", True\n\n            # TODO(mattrobenolt): help, placeholder, title, type\n            results[k.name] = {\n                \"value\": options.get(k.name),\n                \"field\": {\n                    \"default\": k.default(),\n                    \"required\": bool(k.flags & options.FLAG_REQUIRED),\n                    \"disabled\": disabled,\n                    \"disabledReason\": disabled_reason,\n                    \"isSet\": options.isset(k.name),\n                    \"allowEmpty\": bool(k.flags & options.FLAG_ALLOW_EMPTY),\n                },\n            }\n\n        return Response(results)\n\n    def put(self, request):\n        # TODO(dcramer): this should validate options before saving them\n        for k, v in six.iteritems(request.data):\n            if v and isinstance(v, six.string_types):\n                v = v.strip()\n            try:\n                option = options.lookup_key(k)\n            except options.UnknownOption:\n                # TODO(dcramer): unify API errors\n                return Response(\n                    {\"error\": \"unknown_option\", \"errorDetail\": {\"option\": k}}, status=400\n                )\n\n            try:\n                if not (option.flags & options.FLAG_ALLOW_EMPTY) and not v:\n                    options.delete(k)\n                else:\n                    options.set(k, v)\n            except (TypeError, AssertionError) as e:\n                # TODO(chadwhitacre): Use a custom exception for the\n                # immutability case, especially since asserts disappear with\n                # `python -O`.\n                return Response(\n                    {\n                        \"error\": \"invalid_type\" if type(e) is TypeError else \"immutable_option\",\n                        \"errorDetail\": {\"option\": k, \"message\": six.text_type(e)},\n                    },\n                    status=400,\n                )\n        # TODO(dcramer): this has nothing to do with configuring options and\n        # should not be set here\n        options.set(\"sentry:version-configured\", sentry.get_version())\n        return Response(status=200)\n", "path": "src/sentry/api/endpoints/system_options.py"}]}