We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Sentry mail without username and password (reset mail settings)
## Important Details

How are you running Sentry?

Ubuntu 18.04
Installed from pip
Started with systemd

## Description
1.) Set mail configuration via gui (smtp, mailserver, username, password, ...).
2.) We need to change to a local Mailserver without authentication (exim4)
3.) Set in `/etc/sentry/config.yml` the following:
```
mail.backend: 'smtp'  # Use dummy if you want to disable email entirely
mail.host: 'localhost'
mail.port: 25
mail.username: ''
mail.password: ''
mail.use-tls: false
system.admin-email: 'admin@mydomain.com'
```
4.) Sent test E-mail -> `SMTP AUTH extension not supported by server`

The Problem ist that I can not overwrite the username and the passwort that was set previous in the gui with nothing.
If I go to `Admin -> Mail` I see the old values in `username` and `password`, but there should be `not set`.

I tried to find the "old" values in the database, but can't find anything there.
I also greped in the "/www" folder for this values and find nothing.
Where are this values stored?
How can I reset them to "nothing"?

--- 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/options/manager.py`
Content:
```
1 from __future__ import absolute_import, print_function
2 
3 import six
4 import sys
5 import logging
6 
7 from django.conf import settings
8 
9 from sentry.utils.types import type_from_value, Any
10 
11 # Prevent outselves from clobbering the builtin
12 _type = type
13 
14 logger = logging.getLogger("sentry")
15 
16 NoneType = type(None)
17 
18 
19 class UnknownOption(KeyError):
20     pass
21 
22 
23 DEFAULT_FLAGS = 1 << 0
24 # Value can't be changed at runtime
25 FLAG_IMMUTABLE = 1 << 1
26 # Don't check/set in the datastore. Option only exists from file.
27 FLAG_NOSTORE = 1 << 2
28 # Values that should only exist in datastore, and shouldn't exist in
29 # config files.
30 FLAG_STOREONLY = 1 << 3
31 # Values that must be defined for setup to be considered complete
32 FLAG_REQUIRED = 1 << 4
33 # If the value is defined on disk, use that and don't attempt to fetch from db.
34 # This also make the value immutible to changes from web UI.
35 FLAG_PRIORITIZE_DISK = 1 << 5
36 # If the value is allowed to be empty to be considered valid
37 FLAG_ALLOW_EMPTY = 1 << 6
38 
39 # How long will a cache key exist in local memory before being evicted
40 DEFAULT_KEY_TTL = 10
41 # How long will a cache key exist in local memory *after ttl* while the backing store is erroring
42 DEFAULT_KEY_GRACE = 60
43 
44 
45 class OptionsManager(object):
46     """
47     A backend for storing generic configuration within Sentry.
48 
49     Legacy Django configuration should be deprioritized in favor of more dynamic
50     configuration through the options backend, which is backed by a cache and a
51     database.
52 
53     You **always** will receive a response to ``get()``. The response is eventually
54     consistent with the accuracy window depending on the queue workload and you
55     should treat all values as temporary as given a dual connection failure on both
56     the cache and the database the system will fall back to hardcoded defaults.
57 
58     Overall this is a very loose consistency model which is designed to give simple
59     dynamic configuration with maximum uptime, where defaults are always taken from
60     constants in the global configuration.
61     """
62 
63     def __init__(self, store):
64         self.store = store
65         self.registry = {}
66 
67     def set(self, key, value, coerce=True):
68         """
69         Set the value for an option. If the cache is unavailable the action will
70         still suceeed.
71 
72         >>> from sentry import options
73         >>> options.set('option', 'value')
74         """
75         opt = self.lookup_key(key)
76 
77         # If an option isn't able to exist in the store, we can't set it at runtime
78         assert not (opt.flags & FLAG_NOSTORE), "%r cannot be changed at runtime" % key
79         # Enforce immutability on key
80         assert not (opt.flags & FLAG_IMMUTABLE), "%r cannot be changed at runtime" % key
81         # Enforce immutability if value is already set on disk
82         assert not (opt.flags & FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(key)), (
83             "%r cannot be changed at runtime because it is configured on disk" % key
84         )
85 
86         if coerce:
87             value = opt.type(value)
88         elif not opt.type.test(value):
89             raise TypeError("got %r, expected %r" % (_type(value), opt.type))
90 
91         return self.store.set(opt, value)
92 
93     def lookup_key(self, key):
94         try:
95             return self.registry[key]
96         except KeyError:
97             # HACK: Historically, Options were used for random adhoc things.
98             # Fortunately, they all share the same prefix, 'sentry:', so
99             # we special case them here and construct a faux key until we migrate.
100             if key.startswith(("sentry:", "getsentry:")):
101                 logger.debug("Using legacy key: %s", key, exc_info=True)
102                 # History shows, there was an expectation of no types, and empty string
103                 # as the default response value
104                 return self.store.make_key(key, lambda: "", Any, DEFAULT_FLAGS, 0, 0)
105             raise UnknownOption(key)
106 
107     def isset(self, key):
108         """
109         Check if a key has been set to a value and not inheriting from its default.
110         """
111         opt = self.lookup_key(key)
112 
113         if not (opt.flags & FLAG_NOSTORE):
114             result = self.store.get(opt, silent=True)
115             if result is not None:
116                 return True
117 
118         return key in settings.SENTRY_OPTIONS
119 
120     def get(self, key, silent=False):
121         """
122         Get the value of an option, falling back to the local configuration.
123 
124         If no value is present for the key, the default Option value is returned.
125 
126         >>> from sentry import options
127         >>> options.get('option')
128         """
129         # TODO(mattrobenolt): Perform validation on key returned for type Justin Case
130         # values change. This case is unlikely, but good to cover our bases.
131         opt = self.lookup_key(key)
132 
133         # First check if the option should exist on disk, and if it actually
134         # has a value set, let's use that one instead without even attempting
135         # to fetch from network storage.
136         if opt.flags & FLAG_PRIORITIZE_DISK:
137             try:
138                 result = settings.SENTRY_OPTIONS[key]
139             except KeyError:
140                 pass
141             else:
142                 if result:
143                     return result
144 
145         if not (opt.flags & FLAG_NOSTORE):
146             result = self.store.get(opt, silent=silent)
147             if result is not None:
148                 # HACK(mattrobenolt): SENTRY_URL_PREFIX must be kept in sync
149                 # when reading values from the database. This should
150                 # be replaced by a signal.
151                 if key == "system.url-prefix":
152                     settings.SENTRY_URL_PREFIX = result
153                 return result
154 
155         # Some values we don't want to allow them to be configured through
156         # config files and should only exist in the datastore
157         if opt.flags & FLAG_STOREONLY:
158             return opt.default()
159 
160         try:
161             # default to the hardcoded local configuration for this key
162             return settings.SENTRY_OPTIONS[key]
163         except KeyError:
164             try:
165                 return settings.SENTRY_DEFAULT_OPTIONS[key]
166             except KeyError:
167                 return opt.default()
168 
169     def delete(self, key):
170         """
171         Permanently remove the value of an option.
172 
173         This will also clear the value within the store, which means a following
174         get() will result in a miss.
175 
176         >>> from sentry import options
177         >>> options.delete('option')
178         """
179         opt = self.lookup_key(key)
180 
181         # If an option isn't able to exist in the store, we can't set it at runtime
182         assert not (opt.flags & FLAG_NOSTORE), "%r cannot be changed at runtime" % key
183         # Enforce immutability on key
184         assert not (opt.flags & FLAG_IMMUTABLE), "%r cannot be changed at runtime" % key
185 
186         return self.store.delete(opt)
187 
188     def register(
189         self,
190         key,
191         default=None,
192         type=None,
193         flags=DEFAULT_FLAGS,
194         ttl=DEFAULT_KEY_TTL,
195         grace=DEFAULT_KEY_GRACE,
196     ):
197         assert key not in self.registry, "Option already registered: %r" % key
198 
199         if len(key) > 64:
200             raise ValueError("Option key has max length of 64 characters")
201 
202         # If our default is a callable, execute it to
203         # see what value is returns, so we can use that to derive the type
204         if not callable(default):
205             default_value = default
206 
207             def default():
208                 return default_value
209 
210         else:
211             default_value = default()
212 
213         # Guess type based on the default value
214         if type is None:
215             # the default value would be equivilent to '' if no type / default
216             # is specified and we assume six.text_type for safety
217             if default_value is None:
218                 default_value = u""
219 
220                 def default():
221                     return default_value
222 
223             type = type_from_value(default_value)
224 
225         # We disallow None as a value for options since this is ambiguous and doesn't
226         # really make sense as config options. There should be a sensible default
227         # value instead that matches the type expected, rather than relying on None.
228         if type is NoneType:
229             raise TypeError("Options must not be None")
230 
231         # Make sure the type is correct at registration time
232         if default_value is not None and not type.test(default_value):
233             raise TypeError("got %r, expected %r" % (_type(default), type))
234 
235         # If we don't have a default, but we have a type, pull the default
236         # value from the type
237         if default_value is None:
238             default = type
239             default_value = default()
240 
241         # Boolean values need to be set to ALLOW_EMPTY becaues otherwise, "False"
242         # would be treated as a not valid value
243         if default_value is True or default_value is False:
244             flags |= FLAG_ALLOW_EMPTY
245 
246         settings.SENTRY_DEFAULT_OPTIONS[key] = default_value
247 
248         self.registry[key] = self.store.make_key(key, default, type, flags, ttl, grace)
249 
250     def unregister(self, key):
251         try:
252             del self.registry[key]
253         except KeyError:
254             # Raise here or nah?
255             raise UnknownOption(key)
256 
257     def validate(self, options, warn=False):
258         for k, v in six.iteritems(options):
259             try:
260                 self.validate_option(k, v)
261             except UnknownOption as e:
262                 if not warn:
263                     raise
264                 sys.stderr.write("* Unknown config option found: %s\n" % e)
265 
266     def validate_option(self, key, value):
267         opt = self.lookup_key(key)
268         assert not (opt.flags & FLAG_STOREONLY), "%r is not allowed to be loaded from config" % key
269         if not opt.type.test(value):
270             raise TypeError("%r: got %r, expected %r" % (key, _type(value), opt.type))
271 
272     def all(self):
273         """
274         Return an interator for all keys in the registry.
275         """
276         return six.itervalues(self.registry)
277 
278     def filter(self, flag=None):
279         """
280         Return an iterator that's filtered by which flags are set on a key.
281         """
282         if flag is None:
283             return self.all()
284         if flag is DEFAULT_FLAGS:
285             return (k for k in self.all() if k.flags is DEFAULT_FLAGS)
286         return (k for k in self.all() if k.flags & flag)
287
```
--- 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/src/sentry/options/manager.py b/src/sentry/options/manager.py
--- a/src/sentry/options/manager.py
+++ b/src/sentry/options/manager.py
@@ -31,7 +31,7 @@
 # Values that must be defined for setup to be considered complete
 FLAG_REQUIRED = 1 << 4
 # If the value is defined on disk, use that and don't attempt to fetch from db.
-# This also make the value immutible to changes from web UI.
+# This also make the value immutable to changes from web UI.
 FLAG_PRIORITIZE_DISK = 1 << 5
 # If the value is allowed to be empty to be considered valid
 FLAG_ALLOW_EMPTY = 1 << 6
@@ -139,7 +139,7 @@
             except KeyError:
                 pass
             else:
-                if result:
+                if result is not None:
                     return result
 
         if not (opt.flags & FLAG_NOSTORE):

{"golden_diff": "diff --git a/src/sentry/options/manager.py b/src/sentry/options/manager.py\n--- a/src/sentry/options/manager.py\n+++ b/src/sentry/options/manager.py\n@@ -31,7 +31,7 @@\n # Values that must be defined for setup to be considered complete\n FLAG_REQUIRED = 1 << 4\n # If the value is defined on disk, use that and don't attempt to fetch from db.\n-# This also make the value immutible to changes from web UI.\n+# This also make the value immutable to changes from web UI.\n FLAG_PRIORITIZE_DISK = 1 << 5\n # If the value is allowed to be empty to be considered valid\n FLAG_ALLOW_EMPTY = 1 << 6\n@@ -139,7 +139,7 @@\n             except KeyError:\n                 pass\n             else:\n-                if result:\n+                if result is not None:\n                     return result\n \n         if not (opt.flags & FLAG_NOSTORE):\n", "issue": "Sentry mail without username and password (reset mail settings)\n## Important Details\r\n\r\nHow are you running Sentry?\r\n\r\nUbuntu 18.04\r\nInstalled from pip\r\nStarted with systemd\r\n\r\n## Description\r\n1.) Set mail configuration via gui (smtp, mailserver, username, password, ...).\r\n2.) We need to change to a local Mailserver without authentication (exim4)\r\n3.) Set in `/etc/sentry/config.yml` the following:\r\n```\r\nmail.backend: 'smtp'  # Use dummy if you want to disable email entirely\r\nmail.host: 'localhost'\r\nmail.port: 25\r\nmail.username: ''\r\nmail.password: ''\r\nmail.use-tls: false\r\nsystem.admin-email: 'admin@mydomain.com'\r\n```\r\n4.) Sent test E-mail -> `SMTP AUTH extension not supported by server`\r\n\r\nThe Problem ist that I can not overwrite the username and the passwort that was set previous in the gui with nothing.\r\nIf I go to `Admin -> Mail` I see the old values in `username` and `password`, but there should be `not set`.\r\n\r\nI tried to find the \"old\" values in the database, but can't find anything there.\r\nI also greped in the \"/www\" folder for this values and find nothing.\r\nWhere are this values stored?\r\nHow can I reset them to \"nothing\"?\n", "before_files": [{"content": "from __future__ import absolute_import, print_function\n\nimport six\nimport sys\nimport logging\n\nfrom django.conf import settings\n\nfrom sentry.utils.types import type_from_value, Any\n\n# Prevent outselves from clobbering the builtin\n_type = type\n\nlogger = logging.getLogger(\"sentry\")\n\nNoneType = type(None)\n\n\nclass UnknownOption(KeyError):\n    pass\n\n\nDEFAULT_FLAGS = 1 << 0\n# Value can't be changed at runtime\nFLAG_IMMUTABLE = 1 << 1\n# Don't check/set in the datastore. Option only exists from file.\nFLAG_NOSTORE = 1 << 2\n# Values that should only exist in datastore, and shouldn't exist in\n# config files.\nFLAG_STOREONLY = 1 << 3\n# Values that must be defined for setup to be considered complete\nFLAG_REQUIRED = 1 << 4\n# If the value is defined on disk, use that and don't attempt to fetch from db.\n# This also make the value immutible to changes from web UI.\nFLAG_PRIORITIZE_DISK = 1 << 5\n# If the value is allowed to be empty to be considered valid\nFLAG_ALLOW_EMPTY = 1 << 6\n\n# How long will a cache key exist in local memory before being evicted\nDEFAULT_KEY_TTL = 10\n# How long will a cache key exist in local memory *after ttl* while the backing store is erroring\nDEFAULT_KEY_GRACE = 60\n\n\nclass OptionsManager(object):\n    \"\"\"\n    A backend for storing generic configuration within Sentry.\n\n    Legacy Django configuration should be deprioritized in favor of more dynamic\n    configuration through the options backend, which is backed by a cache and a\n    database.\n\n    You **always** will receive a response to ``get()``. The response is eventually\n    consistent with the accuracy window depending on the queue workload and you\n    should treat all values as temporary as given a dual connection failure on both\n    the cache and the database the system will fall back to hardcoded defaults.\n\n    Overall this is a very loose consistency model which is designed to give simple\n    dynamic configuration with maximum uptime, where defaults are always taken from\n    constants in the global configuration.\n    \"\"\"\n\n    def __init__(self, store):\n        self.store = store\n        self.registry = {}\n\n    def set(self, key, value, coerce=True):\n        \"\"\"\n        Set the value for an option. If the cache is unavailable the action will\n        still suceeed.\n\n        >>> from sentry import options\n        >>> options.set('option', 'value')\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        # If an option isn't able to exist in the store, we can't set it at runtime\n        assert not (opt.flags & FLAG_NOSTORE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability on key\n        assert not (opt.flags & FLAG_IMMUTABLE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability if value is already set on disk\n        assert not (opt.flags & FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(key)), (\n            \"%r cannot be changed at runtime because it is configured on disk\" % key\n        )\n\n        if coerce:\n            value = opt.type(value)\n        elif not opt.type.test(value):\n            raise TypeError(\"got %r, expected %r\" % (_type(value), opt.type))\n\n        return self.store.set(opt, value)\n\n    def lookup_key(self, key):\n        try:\n            return self.registry[key]\n        except KeyError:\n            # HACK: Historically, Options were used for random adhoc things.\n            # Fortunately, they all share the same prefix, 'sentry:', so\n            # we special case them here and construct a faux key until we migrate.\n            if key.startswith((\"sentry:\", \"getsentry:\")):\n                logger.debug(\"Using legacy key: %s\", key, exc_info=True)\n                # History shows, there was an expectation of no types, and empty string\n                # as the default response value\n                return self.store.make_key(key, lambda: \"\", Any, DEFAULT_FLAGS, 0, 0)\n            raise UnknownOption(key)\n\n    def isset(self, key):\n        \"\"\"\n        Check if a key has been set to a value and not inheriting from its default.\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        if not (opt.flags & FLAG_NOSTORE):\n            result = self.store.get(opt, silent=True)\n            if result is not None:\n                return True\n\n        return key in settings.SENTRY_OPTIONS\n\n    def get(self, key, silent=False):\n        \"\"\"\n        Get the value of an option, falling back to the local configuration.\n\n        If no value is present for the key, the default Option value is returned.\n\n        >>> from sentry import options\n        >>> options.get('option')\n        \"\"\"\n        # TODO(mattrobenolt): Perform validation on key returned for type Justin Case\n        # values change. This case is unlikely, but good to cover our bases.\n        opt = self.lookup_key(key)\n\n        # First check if the option should exist on disk, and if it actually\n        # has a value set, let's use that one instead without even attempting\n        # to fetch from network storage.\n        if opt.flags & FLAG_PRIORITIZE_DISK:\n            try:\n                result = settings.SENTRY_OPTIONS[key]\n            except KeyError:\n                pass\n            else:\n                if result:\n                    return result\n\n        if not (opt.flags & FLAG_NOSTORE):\n            result = self.store.get(opt, silent=silent)\n            if result is not None:\n                # HACK(mattrobenolt): SENTRY_URL_PREFIX must be kept in sync\n                # when reading values from the database. This should\n                # be replaced by a signal.\n                if key == \"system.url-prefix\":\n                    settings.SENTRY_URL_PREFIX = result\n                return result\n\n        # Some values we don't want to allow them to be configured through\n        # config files and should only exist in the datastore\n        if opt.flags & FLAG_STOREONLY:\n            return opt.default()\n\n        try:\n            # default to the hardcoded local configuration for this key\n            return settings.SENTRY_OPTIONS[key]\n        except KeyError:\n            try:\n                return settings.SENTRY_DEFAULT_OPTIONS[key]\n            except KeyError:\n                return opt.default()\n\n    def delete(self, key):\n        \"\"\"\n        Permanently remove the value of an option.\n\n        This will also clear the value within the store, which means a following\n        get() will result in a miss.\n\n        >>> from sentry import options\n        >>> options.delete('option')\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        # If an option isn't able to exist in the store, we can't set it at runtime\n        assert not (opt.flags & FLAG_NOSTORE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability on key\n        assert not (opt.flags & FLAG_IMMUTABLE), \"%r cannot be changed at runtime\" % key\n\n        return self.store.delete(opt)\n\n    def register(\n        self,\n        key,\n        default=None,\n        type=None,\n        flags=DEFAULT_FLAGS,\n        ttl=DEFAULT_KEY_TTL,\n        grace=DEFAULT_KEY_GRACE,\n    ):\n        assert key not in self.registry, \"Option already registered: %r\" % key\n\n        if len(key) > 64:\n            raise ValueError(\"Option key has max length of 64 characters\")\n\n        # If our default is a callable, execute it to\n        # see what value is returns, so we can use that to derive the type\n        if not callable(default):\n            default_value = default\n\n            def default():\n                return default_value\n\n        else:\n            default_value = default()\n\n        # Guess type based on the default value\n        if type is None:\n            # the default value would be equivilent to '' if no type / default\n            # is specified and we assume six.text_type for safety\n            if default_value is None:\n                default_value = u\"\"\n\n                def default():\n                    return default_value\n\n            type = type_from_value(default_value)\n\n        # We disallow None as a value for options since this is ambiguous and doesn't\n        # really make sense as config options. There should be a sensible default\n        # value instead that matches the type expected, rather than relying on None.\n        if type is NoneType:\n            raise TypeError(\"Options must not be None\")\n\n        # Make sure the type is correct at registration time\n        if default_value is not None and not type.test(default_value):\n            raise TypeError(\"got %r, expected %r\" % (_type(default), type))\n\n        # If we don't have a default, but we have a type, pull the default\n        # value from the type\n        if default_value is None:\n            default = type\n            default_value = default()\n\n        # Boolean values need to be set to ALLOW_EMPTY becaues otherwise, \"False\"\n        # would be treated as a not valid value\n        if default_value is True or default_value is False:\n            flags |= FLAG_ALLOW_EMPTY\n\n        settings.SENTRY_DEFAULT_OPTIONS[key] = default_value\n\n        self.registry[key] = self.store.make_key(key, default, type, flags, ttl, grace)\n\n    def unregister(self, key):\n        try:\n            del self.registry[key]\n        except KeyError:\n            # Raise here or nah?\n            raise UnknownOption(key)\n\n    def validate(self, options, warn=False):\n        for k, v in six.iteritems(options):\n            try:\n                self.validate_option(k, v)\n            except UnknownOption as e:\n                if not warn:\n                    raise\n                sys.stderr.write(\"* Unknown config option found: %s\\n\" % e)\n\n    def validate_option(self, key, value):\n        opt = self.lookup_key(key)\n        assert not (opt.flags & FLAG_STOREONLY), \"%r is not allowed to be loaded from config\" % key\n        if not opt.type.test(value):\n            raise TypeError(\"%r: got %r, expected %r\" % (key, _type(value), opt.type))\n\n    def all(self):\n        \"\"\"\n        Return an interator for all keys in the registry.\n        \"\"\"\n        return six.itervalues(self.registry)\n\n    def filter(self, flag=None):\n        \"\"\"\n        Return an iterator that's filtered by which flags are set on a key.\n        \"\"\"\n        if flag is None:\n            return self.all()\n        if flag is DEFAULT_FLAGS:\n            return (k for k in self.all() if k.flags is DEFAULT_FLAGS)\n        return (k for k in self.all() if k.flags & flag)\n", "path": "src/sentry/options/manager.py"}], "after_files": [{"content": "from __future__ import absolute_import, print_function\n\nimport six\nimport sys\nimport logging\n\nfrom django.conf import settings\n\nfrom sentry.utils.types import type_from_value, Any\n\n# Prevent outselves from clobbering the builtin\n_type = type\n\nlogger = logging.getLogger(\"sentry\")\n\nNoneType = type(None)\n\n\nclass UnknownOption(KeyError):\n    pass\n\n\nDEFAULT_FLAGS = 1 << 0\n# Value can't be changed at runtime\nFLAG_IMMUTABLE = 1 << 1\n# Don't check/set in the datastore. Option only exists from file.\nFLAG_NOSTORE = 1 << 2\n# Values that should only exist in datastore, and shouldn't exist in\n# config files.\nFLAG_STOREONLY = 1 << 3\n# Values that must be defined for setup to be considered complete\nFLAG_REQUIRED = 1 << 4\n# If the value is defined on disk, use that and don't attempt to fetch from db.\n# This also make the value immutable to changes from web UI.\nFLAG_PRIORITIZE_DISK = 1 << 5\n# If the value is allowed to be empty to be considered valid\nFLAG_ALLOW_EMPTY = 1 << 6\n\n# How long will a cache key exist in local memory before being evicted\nDEFAULT_KEY_TTL = 10\n# How long will a cache key exist in local memory *after ttl* while the backing store is erroring\nDEFAULT_KEY_GRACE = 60\n\n\nclass OptionsManager(object):\n    \"\"\"\n    A backend for storing generic configuration within Sentry.\n\n    Legacy Django configuration should be deprioritized in favor of more dynamic\n    configuration through the options backend, which is backed by a cache and a\n    database.\n\n    You **always** will receive a response to ``get()``. The response is eventually\n    consistent with the accuracy window depending on the queue workload and you\n    should treat all values as temporary as given a dual connection failure on both\n    the cache and the database the system will fall back to hardcoded defaults.\n\n    Overall this is a very loose consistency model which is designed to give simple\n    dynamic configuration with maximum uptime, where defaults are always taken from\n    constants in the global configuration.\n    \"\"\"\n\n    def __init__(self, store):\n        self.store = store\n        self.registry = {}\n\n    def set(self, key, value, coerce=True):\n        \"\"\"\n        Set the value for an option. If the cache is unavailable the action will\n        still suceeed.\n\n        >>> from sentry import options\n        >>> options.set('option', 'value')\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        # If an option isn't able to exist in the store, we can't set it at runtime\n        assert not (opt.flags & FLAG_NOSTORE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability on key\n        assert not (opt.flags & FLAG_IMMUTABLE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability if value is already set on disk\n        assert not (opt.flags & FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(key)), (\n            \"%r cannot be changed at runtime because it is configured on disk\" % key\n        )\n\n        if coerce:\n            value = opt.type(value)\n        elif not opt.type.test(value):\n            raise TypeError(\"got %r, expected %r\" % (_type(value), opt.type))\n\n        return self.store.set(opt, value)\n\n    def lookup_key(self, key):\n        try:\n            return self.registry[key]\n        except KeyError:\n            # HACK: Historically, Options were used for random adhoc things.\n            # Fortunately, they all share the same prefix, 'sentry:', so\n            # we special case them here and construct a faux key until we migrate.\n            if key.startswith((\"sentry:\", \"getsentry:\")):\n                logger.debug(\"Using legacy key: %s\", key, exc_info=True)\n                # History shows, there was an expectation of no types, and empty string\n                # as the default response value\n                return self.store.make_key(key, lambda: \"\", Any, DEFAULT_FLAGS, 0, 0)\n            raise UnknownOption(key)\n\n    def isset(self, key):\n        \"\"\"\n        Check if a key has been set to a value and not inheriting from its default.\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        if not (opt.flags & FLAG_NOSTORE):\n            result = self.store.get(opt, silent=True)\n            if result is not None:\n                return True\n\n        return key in settings.SENTRY_OPTIONS\n\n    def get(self, key, silent=False):\n        \"\"\"\n        Get the value of an option, falling back to the local configuration.\n\n        If no value is present for the key, the default Option value is returned.\n\n        >>> from sentry import options\n        >>> options.get('option')\n        \"\"\"\n        # TODO(mattrobenolt): Perform validation on key returned for type Justin Case\n        # values change. This case is unlikely, but good to cover our bases.\n        opt = self.lookup_key(key)\n\n        # First check if the option should exist on disk, and if it actually\n        # has a value set, let's use that one instead without even attempting\n        # to fetch from network storage.\n        if opt.flags & FLAG_PRIORITIZE_DISK:\n            try:\n                result = settings.SENTRY_OPTIONS[key]\n            except KeyError:\n                pass\n            else:\n                if result is not None:\n                    return result\n\n        if not (opt.flags & FLAG_NOSTORE):\n            result = self.store.get(opt, silent=silent)\n            if result is not None:\n                # HACK(mattrobenolt): SENTRY_URL_PREFIX must be kept in sync\n                # when reading values from the database. This should\n                # be replaced by a signal.\n                if key == \"system.url-prefix\":\n                    settings.SENTRY_URL_PREFIX = result\n                return result\n\n        # Some values we don't want to allow them to be configured through\n        # config files and should only exist in the datastore\n        if opt.flags & FLAG_STOREONLY:\n            return opt.default()\n\n        try:\n            # default to the hardcoded local configuration for this key\n            return settings.SENTRY_OPTIONS[key]\n        except KeyError:\n            try:\n                return settings.SENTRY_DEFAULT_OPTIONS[key]\n            except KeyError:\n                return opt.default()\n\n    def delete(self, key):\n        \"\"\"\n        Permanently remove the value of an option.\n\n        This will also clear the value within the store, which means a following\n        get() will result in a miss.\n\n        >>> from sentry import options\n        >>> options.delete('option')\n        \"\"\"\n        opt = self.lookup_key(key)\n\n        # If an option isn't able to exist in the store, we can't set it at runtime\n        assert not (opt.flags & FLAG_NOSTORE), \"%r cannot be changed at runtime\" % key\n        # Enforce immutability on key\n        assert not (opt.flags & FLAG_IMMUTABLE), \"%r cannot be changed at runtime\" % key\n\n        return self.store.delete(opt)\n\n    def register(\n        self,\n        key,\n        default=None,\n        type=None,\n        flags=DEFAULT_FLAGS,\n        ttl=DEFAULT_KEY_TTL,\n        grace=DEFAULT_KEY_GRACE,\n    ):\n        assert key not in self.registry, \"Option already registered: %r\" % key\n\n        if len(key) > 64:\n            raise ValueError(\"Option key has max length of 64 characters\")\n\n        # If our default is a callable, execute it to\n        # see what value is returns, so we can use that to derive the type\n        if not callable(default):\n            default_value = default\n\n            def default():\n                return default_value\n\n        else:\n            default_value = default()\n\n        # Guess type based on the default value\n        if type is None:\n            # the default value would be equivilent to '' if no type / default\n            # is specified and we assume six.text_type for safety\n            if default_value is None:\n                default_value = u\"\"\n\n                def default():\n                    return default_value\n\n            type = type_from_value(default_value)\n\n        # We disallow None as a value for options since this is ambiguous and doesn't\n        # really make sense as config options. There should be a sensible default\n        # value instead that matches the type expected, rather than relying on None.\n        if type is NoneType:\n            raise TypeError(\"Options must not be None\")\n\n        # Make sure the type is correct at registration time\n        if default_value is not None and not type.test(default_value):\n            raise TypeError(\"got %r, expected %r\" % (_type(default), type))\n\n        # If we don't have a default, but we have a type, pull the default\n        # value from the type\n        if default_value is None:\n            default = type\n            default_value = default()\n\n        # Boolean values need to be set to ALLOW_EMPTY becaues otherwise, \"False\"\n        # would be treated as a not valid value\n        if default_value is True or default_value is False:\n            flags |= FLAG_ALLOW_EMPTY\n\n        settings.SENTRY_DEFAULT_OPTIONS[key] = default_value\n\n        self.registry[key] = self.store.make_key(key, default, type, flags, ttl, grace)\n\n    def unregister(self, key):\n        try:\n            del self.registry[key]\n        except KeyError:\n            # Raise here or nah?\n            raise UnknownOption(key)\n\n    def validate(self, options, warn=False):\n        for k, v in six.iteritems(options):\n            try:\n                self.validate_option(k, v)\n            except UnknownOption as e:\n                if not warn:\n                    raise\n                sys.stderr.write(\"* Unknown config option found: %s\\n\" % e)\n\n    def validate_option(self, key, value):\n        opt = self.lookup_key(key)\n        assert not (opt.flags & FLAG_STOREONLY), \"%r is not allowed to be loaded from config\" % key\n        if not opt.type.test(value):\n            raise TypeError(\"%r: got %r, expected %r\" % (key, _type(value), opt.type))\n\n    def all(self):\n        \"\"\"\n        Return an interator for all keys in the registry.\n        \"\"\"\n        return six.itervalues(self.registry)\n\n    def filter(self, flag=None):\n        \"\"\"\n        Return an iterator that's filtered by which flags are set on a key.\n        \"\"\"\n        if flag is None:\n            return self.all()\n        if flag is DEFAULT_FLAGS:\n            return (k for k in self.all() if k.flags is DEFAULT_FLAGS)\n        return (k for k in self.all() if k.flags & flag)\n", "path": "src/sentry/options/manager.py"}]}