text: from __future__ import absolute_import, print_function
  
  import six
  import sys
  import logging
  
  from django.conf import settings
  
  from sentry.utils.types import type_from_value, Any
  
  # Prevent outselves from clobbering the builtin
  _type = type
  
  logger = logging.getLogger('sentry')
  
  NoneType = type(None)
  
  
  class UnknownOption(KeyError):
      pass
  
  
  DEFAULT_FLAGS = 1 << 0
  # Value can't be changed at runtime
  FLAG_IMMUTABLE = 1 << 1
  # Don't check/set in the datastore. Option only exists from file.
  FLAG_NOSTORE = 1 << 2
  # Values that should only exist in datastore, and shouldn't exist in
  # config files.
  FLAG_STOREONLY = 1 << 3
  # 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.
  FLAG_PRIORITIZE_DISK = 1 << 5
  # If the value is allowed to be empty to be considered valid
  FLAG_ALLOW_EMPTY = 1 << 6
  
  # How long will a cache key exist in local memory before being evicted
  DEFAULT_KEY_TTL = 10
  # How long will a cache key exist in local memory *after ttl* while the backing store is erroring
  DEFAULT_KEY_GRACE = 60
  
  
  class OptionsManager(object):
      """
      A backend for storing generic configuration within Sentry.
  
      Legacy Django configuration should be deprioritized in favor of more dynamic
      configuration through the options backend, which is backed by a cache and a
      database.
  
      You **always** will receive a response to ``get()``. The response is eventually
      consistent with the accuracy window depending on the queue workload and you
      should treat all values as temporary as given a dual connection failure on both
      the cache and the database the system will fall back to hardcoded defaults.
  
      Overall this is a very loose consistency model which is designed to give simple
      dynamic configuration with maximum uptime, where defaults are always taken from
      constants in the global configuration.
      """
  
      def __init__(self, store):
          self.store = store
          self.registry = {}
  
      def set(self, key, value, coerce=True):
          """
          Set the value for an option. If the cache is unavailable the action will
          still suceeed.
  
          >>> from sentry import options
          >>> options.set('option', 'value')
          """
          opt = self.lookup_key(key)
  
          # If an option isn't able to exist in the store, we can't set it at runtime
          assert not (opt.flags & FLAG_NOSTORE), '%r cannot be changed at runtime' % key
          # Enforce immutability on key
          assert not (opt.flags & FLAG_IMMUTABLE), '%r cannot be changed at runtime' % key
          # Enforce immutability if value is already set on disk
          assert not (opt.flags & FLAG_PRIORITIZE_DISK and settings.SENTRY_OPTIONS.get(key)
                      ), '%r cannot be changed at runtime because it is configured on disk' % key
  
          if coerce:
              value = opt.type(value)
          elif not opt.type.test(value):
              raise TypeError('got %r, expected %r' % (_type(value), opt.type))
  
          return self.store.set(opt, value)
  
      def lookup_key(self, key):
          try:
              return self.registry[key]
          except KeyError:
              # HACK: Historically, Options were used for random adhoc things.
              # Fortunately, they all share the same prefix, 'sentry:', so
              # we special case them here and construct a faux key until we migrate.
              if key.startswith(('sentry:', 'getsentry:')):
                  logger.debug('Using legacy key: %s', key, exc_info=True)
                  # History shows, there was an expectation of no types, and empty string
                  # as the default response value
                  return self.store.make_key(key, lambda: '', Any, DEFAULT_FLAGS, 0, 0)
              raise UnknownOption(key)
  
      def isset(self, key):
          """
          Check if a key has been set to a value and not inheriting from its default.
          """
          opt = self.lookup_key(key)
  
          if not (opt.flags & FLAG_NOSTORE):
              result = self.store.get(opt, silent=True)
              if result is not None:
                  return True
  
          return key in settings.SENTRY_OPTIONS
  
      def get(self, key, silent=False):
          """
          Get the value of an option, falling back to the local configuration.
  
          If no value is present for the key, the default Option value is returned.
  
          >>> from sentry import options
          >>> options.get('option')
          """
          # TODO(mattrobenolt): Perform validation on key returned for type Justin Case
          # values change. This case is unlikely, but good to cover our bases.
          opt = self.lookup_key(key)
  
          # First check if the option should exist on disk, and if it actually
          # has a value set, let's use that one instead without even attempting
          # to fetch from network storage.
          if opt.flags & FLAG_PRIORITIZE_DISK:
              try:
                  result = settings.SENTRY_OPTIONS[key]
              except KeyError:
                  pass
              else:
                  if result:
                      return result
  
          if not (opt.flags & FLAG_NOSTORE):
              result = self.store.get(opt, silent=silent)
              if result is not None:
                  # HACK(mattrobenolt): SENTRY_URL_PREFIX must be kept in sync
                  # when reading values from the database. This should
                  # be replaced by a signal.
                  if key == 'system.url-prefix':
                      settings.SENTRY_URL_PREFIX = result
                  return result
  
          # Some values we don't want to allow them to be configured through
          # config files and should only exist in the datastore
          if opt.flags & FLAG_STOREONLY:
              return opt.default()
  
          try:
              # default to the hardcoded local configuration for this key
              return settings.SENTRY_OPTIONS[key]
          except KeyError:
              try:
                  return settings.SENTRY_DEFAULT_OPTIONS[key]
              except KeyError:
                  return opt.default()
  
      def delete(self, key):
          """
          Permanently remove the value of an option.
  
          This will also clear the value within the store, which means a following
          get() will result in a miss.
  
          >>> from sentry import options
          >>> options.delete('option')
          """
          opt = self.lookup_key(key)
  
          # If an option isn't able to exist in the store, we can't set it at runtime
          assert not (opt.flags & FLAG_NOSTORE), '%r cannot be changed at runtime' % key
          # Enforce immutability on key
          assert not (opt.flags & FLAG_IMMUTABLE), '%r cannot be changed at runtime' % key
  
          return self.store.delete(opt)
  
      def register(
          self,
          key,
          default=None,
          type=None,
          flags=DEFAULT_FLAGS,
          ttl=DEFAULT_KEY_TTL,
          grace=DEFAULT_KEY_GRACE
      ):
          assert key not in self.registry, 'Option already registered: %r' % key
  
          if len(key) > 64:
              raise ValueError('Option key has max length of 64 characters')
  
          # If our default is a callable, execute it to
          # see what value is returns, so we can use that to derive the type
          if not callable(default):
              default_value = default
  
              def default():
                  return default_value
          else:
              default_value = default()
  
          # Guess type based on the default value
          if type is None:
              # the default value would be equivilent to '' if no type / default
              # is specified and we assume six.text_type for safety
              if default_value is None:
                  default_value = u''
  
                  def default():
                      return default_value
  
              type = type_from_value(default_value)
  
          # We disallow None as a value for options since this is ambiguous and doesn't
          # really make sense as config options. There should be a sensible default
          # value instead that matches the type expected, rather than relying on None.
          if type is NoneType:
              raise TypeError('Options must not be None')
  
          # Make sure the type is correct at registration time
          if default_value is not None and not type.test(default_value):
              raise TypeError('got %r, expected %r' % (_type(default), type))
  
          # If we don't have a default, but we have a type, pull the default
          # value from the type
          if default_value is None:
              default = type
              default_value = default()
  
          # Boolean values need to be set to ALLOW_EMPTY becaues otherwise, "False"
          # would be treated as a not valid value
          if default_value is True or default_value is False:
              flags |= FLAG_ALLOW_EMPTY
  
          settings.SENTRY_DEFAULT_OPTIONS[key] = default_value
  
          self.registry[key] = self.store.make_key(key, default, type, flags, ttl, grace)
  
      def unregister(self, key):
          try:
              del self.registry[key]
          except KeyError:
              # Raise here or nah?
              raise UnknownOption(key)
  
      def validate(self, options, warn=False):
          for k, v in six.iteritems(options):
              try:
                  self.validate_option(k, v)
              except UnknownOption as e:
                  if not warn:
                      raise
                  sys.stderr.write('* Unknown config option found: %s\n' % e)
  
      def validate_option(self, key, value):
          opt = self.lookup_key(key)
          assert not (opt.flags & FLAG_STOREONLY), '%r is not allowed to be loaded from config' % key
          if not opt.type.test(value):
              raise TypeError('%r: got %r, expected %r' % (key, _type(value), opt.type))
  
      def all(self):
          """
          Return an interator for all keys in the registry.
          """
          return six.itervalues(self.registry)
  
      def filter(self, flag=None):
          """
          Return an iterator that's filtered by which flags are set on a key.
          """
          if flag is None:
              return self.all()
          if flag is DEFAULT_FLAGS:
              return (k for k in self.all() if k.flags is DEFAULT_FLAGS)
          return (k for k in self.all() if k.flags & flag)
  