Repository: Kinto/kinto

Issue:
Dissociate settings prefix from project name
We'd like to use the project name in the Kinto Admin UI.

It could make sense to dissociate the project name from the settings prefix 

See https://github.com/Kinto/kinto-admin/issues/655


Discussion:

Resolving patch:
diff --git a/kinto/__init__.py b/kinto/__init__.py
--- a/kinto/__init__.py
+++ b/kinto/__init__.py
@@ -35,7 +35,8 @@
     'bucket_id_generator': 'kinto.views.NameGenerator',
     'collection_id_generator': 'kinto.views.NameGenerator',
     'group_id_generator': 'kinto.views.NameGenerator',
-    'record_id_generator': 'kinto.views.RelaxedUUID'
+    'record_id_generator': 'kinto.views.RelaxedUUID',
+    'project_name': 'kinto',
 }
 
 
@@ -45,8 +46,8 @@ def main(global_config, config=None, **settings):
 
     config.registry.command = global_config and global_config.get('command', None)
 
-    # Force project name, since it determines settings prefix.
-    config.add_settings({'kinto.project_name': 'kinto'})
+    # Force settings prefix.
+    config.add_settings({'kinto.settings_prefix': 'kinto'})
 
     kinto.core.initialize(config,
                           version=__version__,
diff --git a/kinto/core/__init__.py b/kinto/core/__init__.py
--- a/kinto/core/__init__.py
+++ b/kinto/core/__init__.py
@@ -76,6 +76,7 @@
     'project_version': '',
     'readonly': False,
     'retry_after_seconds': 30,
+    'settings_prefix': '',
     'statsd_backend': 'kinto.core.statsd',
     'statsd_prefix': 'kinto.core',
     'statsd_url': None,
diff --git a/kinto/core/initialization.py b/kinto/core/initialization.py
--- a/kinto/core/initialization.py
+++ b/kinto/core/initialization.py
@@ -421,7 +421,7 @@ def setup_listeners(config):
 
     write_actions = (ACTIONS.CREATE, ACTIONS.UPDATE, ACTIONS.DELETE)
     settings = config.get_settings()
-    project_name = settings.get('project_name', '')
+    settings_prefix = settings.get('settings_prefix', '')
     listeners = aslist(settings['event_listeners'])
 
     for name in listeners:
@@ -435,7 +435,7 @@ def setup_listeners(config):
         except (ImportError, AttributeError):
             module_setting = prefix + 'use'
             # Read from ENV or settings.
-            module_value = utils.read_env('{}.{}'.format(project_name, module_setting),
+            module_value = utils.read_env('{}.{}'.format(settings_prefix, module_setting),
                                           settings.get(module_setting))
             listener_mod = config.maybe_dotted(module_value)
             listener = listener_mod.load_from_config(config, prefix)
@@ -449,7 +449,7 @@ def setup_listeners(config):
         # Optional filter by event action.
         actions_setting = prefix + 'actions'
         # Read from ENV or settings.
-        actions_value = utils.read_env('{}.{}'.format(project_name, actions_setting),
+        actions_value = utils.read_env('{}.{}'.format(settings_prefix, actions_setting),
                                        settings.get(actions_setting, ''))
         actions = aslist(actions_value)
         if len(actions) > 0:
@@ -460,7 +460,7 @@ def setup_listeners(config):
         # Optional filter by event resource name.
         resource_setting = prefix + 'resources'
         # Read from ENV or settings.
-        resource_value = utils.read_env('{}.{}'.format(project_name, resource_setting),
+        resource_value = utils.read_env('{}.{}'.format(settings_prefix, resource_setting),
                                         settings.get(resource_setting, ''))
         resource_names = aslist(resource_value)
 
@@ -481,25 +481,24 @@ def load_default_settings(config, default_settings):
     """
     settings = config.get_settings()
 
-    project_name = settings['project_name']
+    settings_prefix = settings['settings_prefix']
 
     def _prefixed_keys(key):
         unprefixed = key
-        if key.startswith('kinto.') or key.startswith(project_name + '.'):
+        if key.startswith(settings_prefix + '.'):
             unprefixed = key.split('.', 1)[1]
-        project_prefix = '{}.{}'.format(project_name, unprefixed)
-        kinto_prefix = 'kinto.{}'.format(unprefixed)
-        return unprefixed, project_prefix, kinto_prefix
+        project_prefix = '{}.{}'.format(settings_prefix, unprefixed)
+        return unprefixed, project_prefix
 
     # Fill settings with default values if not defined.
     for key, default_value in sorted(default_settings.items()):
-        unprefixed, project_prefix, kinto_prefix = keys = _prefixed_keys(key)
+        unprefixed, project_prefix = keys = _prefixed_keys(key)
         is_defined = len(set(settings.keys()).intersection(set(keys))) > 0
         if not is_defined:
             settings[unprefixed] = default_value
 
     for key, value in sorted(settings.items()):
-        unprefixed, project_prefix, kinto_prefix = keys = _prefixed_keys(key)
+        unprefixed, project_prefix = keys = _prefixed_keys(key)
 
         # Fail if not only one is defined.
         defined = set(settings.keys()).intersection(set(keys))
@@ -513,14 +512,13 @@ def _prefixed_keys(key):
         # e.g. HTTP_PORT, READINGLIST_HTTP_PORT, KINTO_HTTP_PORT
         from_env = utils.read_env(unprefixed, value)
         from_env = utils.read_env(project_prefix, from_env)
-        from_env = utils.read_env(kinto_prefix, from_env)
 
         settings[unprefixed] = from_env
 
     config.add_settings(settings)
 
 
-def initialize(config, version=None, project_name='', default_settings=None):
+def initialize(config, version=None, settings_prefix='', default_settings=None):
     """Initialize kinto.core with the given configuration, version and project
     name.
 
@@ -531,7 +529,7 @@ def initialize(config, version=None, project_name='', default_settings=None):
     :type config: ~pyramid:pyramid.config.Configurator
     :param str version: Current project version (e.g. '0.0.1') if not defined
         in application settings.
-    :param str project_name: Project name if not defined
+    :param str settings_prefix: Project name if not defined
         in application settings.
     :param dict default_settings: Override kinto.core default settings values.
     """
@@ -539,11 +537,11 @@ def initialize(config, version=None, project_name='', default_settings=None):
 
     settings = config.get_settings()
 
-    project_name = settings.pop('kinto.project_name',
-                                settings.get('project_name')) or project_name
-    settings['project_name'] = project_name
-    if not project_name:
-        warnings.warn('No value specified for `project_name`')
+    settings_prefix = settings.pop('kinto.settings_prefix',
+                                   settings.get('settings_prefix')) or settings_prefix
+    settings['settings_prefix'] = settings_prefix
+    if not settings_prefix:
+        warnings.warn('No value specified for `settings_prefix`')
 
     kinto_core_defaults = {**DEFAULT_SETTINGS}
 


Test patch:
diff --git a/tests/core/test_initialization.py b/tests/core/test_initialization.py
--- a/tests/core/test_initialization.py
+++ b/tests/core/test_initialization.py
@@ -26,7 +26,7 @@ def test_set_the_project_version_if_specified(self):
         self.assertEqual(config.registry.settings['project_version'], '0.0.1')
 
     def test_project_version_uses_setting_if_specified(self):
-        config = Configurator(settings={'kinto.project_version': '1.0.0'})
+        config = Configurator(settings={'name.project_version': '1.0.0'})
         kinto.core.initialize(config, '0.0.1', 'name')
         self.assertEqual(config.registry.settings['project_version'], '1.0.0')
 
@@ -36,39 +36,39 @@ def test_http_api_version_relies_on_project_version_by_default(self):
         self.assertEqual(config.registry.settings['http_api_version'], '0.1')
 
     def test_http_api_version_uses_setting_if_specified(self):
-        config = Configurator(settings={'kinto.http_api_version': '1.3'})
+        config = Configurator(settings={'name.http_api_version': '1.3'})
         kinto.core.initialize(config, '0.0.1', 'name')
         self.assertEqual(config.registry.settings['http_api_version'], '1.3')
 
-    def test_warns_if_project_name_is_empty(self):
-        config = Configurator(settings={'kinto.project_name': ''})
+    def test_warns_if_settings_prefix_is_empty(self):
+        config = Configurator(settings={'kinto.settings_prefix': ''})
         with mock.patch('kinto.core.initialization.warnings.warn') as mocked:
             kinto.core.initialize(config, '0.0.1')
-            error_msg = 'No value specified for `project_name`'
+            error_msg = 'No value specified for `settings_prefix`'
             mocked.assert_any_call(error_msg)
 
-    def test_warns_if_project_name_is_missing(self):
+    def test_warns_if_settings_prefix_is_missing(self):
         config = Configurator()
         with mock.patch('kinto.core.initialization.warnings.warn') as mocked:
             kinto.core.initialize(config, '0.0.1')
-            error_msg = 'No value specified for `project_name`'
+            error_msg = 'No value specified for `settings_prefix`'
             mocked.assert_any_call(error_msg)
 
-    def test_set_the_project_name_if_specified(self):
+    def test_set_the_settings_prefix_if_specified(self):
         config = Configurator()
         kinto.core.initialize(config, '0.0.1', 'kinto')
-        self.assertEqual(config.registry.settings['project_name'],
+        self.assertEqual(config.registry.settings['settings_prefix'],
                          'kinto')
 
-    def test_set_the_project_name_from_settings_even_if_specified(self):
-        config = Configurator(settings={'kinto.project_name': 'kinto'})
+    def test_set_the_settings_prefix_from_settings_even_if_specified(self):
+        config = Configurator(settings={'kinto.settings_prefix': 'kinto'})
         kinto.core.initialize(config, '0.0.1', 'readinglist')
-        self.assertEqual(config.registry.settings['project_name'],
+        self.assertEqual(config.registry.settings['settings_prefix'],
                          'kinto')
 
     def test_warns_if_not_https(self):
         with mock.patch('kinto.core.initialization.warnings.warn') as mocked:
-            config = Configurator(settings={'project_name': 'kinto',
+            config = Configurator(settings={'settings_prefix': 'kinto',
                                             'kinto.http_scheme': 'https'})
             kinto.core.initialize(config, '0.0.1')
             self.assertFalse(mocked.called)
@@ -80,26 +80,25 @@ def test_warns_if_not_https(self):
 
     def test_default_settings_are_overriden_if_specified_in_initialize(self):
         config = Configurator()
-        defaults = {'kinto.paginate_by': 102}
-        kinto.core.initialize(config, '0.0.1', 'project_name',
-                              default_settings=defaults)
+        defaults = {'paginate_by': 102}
+        kinto.core.initialize(config, '0.0.1', 'prefix', default_settings=defaults)
         self.assertEqual(config.registry.settings['paginate_by'], 102)
 
     def test_default_settings_are_overriden_by_application(self):
-        config = Configurator(settings={'kinto.paginate_by': 10})
-        kinto.core.initialize(config, '0.0.1', 'project_name')
+        config = Configurator(settings={'prefix.paginate_by': 10})
+        kinto.core.initialize(config, '0.0.1', 'prefix')
         self.assertEqual(config.registry.settings['paginate_by'], 10)
 
     def test_specified_default_settings_are_overriden_by_application(self):
-        config = Configurator(settings={'kinto.paginate_by': 5})
-        defaults = {'kinto.paginate_by': 10}
-        kinto.core.initialize(config, '0.0.1', 'project_name',
+        config = Configurator(settings={'settings_prefix.paginate_by': 5})
+        defaults = {'settings_prefix.paginate_by': 10}
+        kinto.core.initialize(config, '0.0.1', 'settings_prefix',
                               default_settings=defaults)
         self.assertEqual(config.registry.settings['paginate_by'], 5)
 
     def test_backends_are_not_instantiated_by_default(self):
         config = Configurator(settings={**kinto.core.DEFAULT_SETTINGS})
-        kinto.core.initialize(config, '0.0.1', 'project_name')
+        kinto.core.initialize(config, '0.0.1', 'settings_prefix')
         self.assertFalse(hasattr(config.registry, 'storage'))
         self.assertFalse(hasattr(config.registry, 'cache'))
         self.assertFalse(hasattr(config.registry, 'permission'))
@@ -108,25 +107,25 @@ def test_backends_type_is_checked_when_instantiated(self):
         def config_fails(settings):
             config = Configurator(settings=settings)
             with self.assertRaises(ConfigurationError):
-                kinto.core.initialize(config, '0.0.1', 'project_name')
+                kinto.core.initialize(config, '0.0.1', 'settings_prefix')
 
-        config_fails({'kinto.storage_backend': 'kinto.core.cache.memory'})
-        config_fails({'kinto.cache_backend': 'kinto.core.storage.memory'})
-        config_fails({'kinto.permission_backend': 'kinto.core.storage.memory'})
+        config_fails({'settings_prefix.storage_backend': 'kinto.core.cache.memory'})
+        config_fails({'settings_prefix.cache_backend': 'kinto.core.storage.memory'})
+        config_fails({'settings_prefix.permission_backend': 'kinto.core.storage.memory'})
 
     def test_environment_values_override_configuration(self):
         import os
 
-        envkey = 'KINTO_PROJECT_NAME'
+        envkey = 'KINTO_SETTINGS_PREFIX'
         os.environ[envkey] = 'abc'
 
-        config = Configurator(settings={'kinto.project_name': 'kinto'})
+        config = Configurator(settings={'kinto.settings_prefix': 'kinto'})
         kinto.core.initialize(config, '0.0.1')
 
         os.environ.pop(envkey)
 
-        project_used = config.registry.settings['project_name']
-        self.assertEqual(project_used, 'abc')
+        prefix_used = config.registry.settings['settings_prefix']
+        self.assertEqual(prefix_used, 'abc')
 
 
 class ProjectSettingsTest(unittest.TestCase):
@@ -141,7 +140,7 @@ def test_uses_unprefixed_name(self):
         }
         self.assertEqual(self.settings(settings)['paginate_by'], 3.14)
 
-    def test_uses_project_name(self):
+    def test_uses_settings_prefix(self):
         settings = {
             'kinto.paginate_by': 42,
         }
@@ -168,7 +167,7 @@ def test_raises_valueerror_if_different_multiple_entries(self):
         with self.assertRaises(ValueError):
             self.settings(settings)
 
-    def test_environment_can_specify_project_name(self):
+    def test_environment_can_specify_settings_prefix(self):
         import os
 
         envkey = 'KINTO_STORAGE_BACKEND'
@@ -231,12 +230,12 @@ def test_profiler_is_installed_if_set_to_true(self, mocked_profiler):
     def test_load_default_settings_handle_prefix_attributes(self):
         config = mock.MagicMock()
 
-        settings = {'project_name': 'myapp'}
+        settings = {'settings_prefix': 'myapp'}
 
         config.get_settings.return_value = settings
         initialization.load_default_settings(
             config, {'multiauth.policies': 'basicauth',
-                     'kinto.http_scheme': 'https',
+                     'myapp.http_scheme': 'https',
                      'myapp.http_host': 'localhost:8888'})
 
         self.assertIn('multiauth.policies', settings)
@@ -308,19 +307,19 @@ def test_statsd_is_set_on_authentication(self):
         c.watch_execution_time.assert_any_call(None, prefix='authentication')
 
     def test_statsd_counts_nothing_on_anonymous_requests(self):
-        kinto.core.initialize(self.config, '0.0.1', 'project_name')
+        kinto.core.initialize(self.config, '0.0.1', 'settings_prefix')
         app = webtest.TestApp(self.config.make_wsgi_app())
         app.get('/')
         self.assertFalse(self.mocked.count.called)
 
     def test_statsd_counts_views_and_methods(self):
-        kinto.core.initialize(self.config, '0.0.1', 'project_name')
+        kinto.core.initialize(self.config, '0.0.1', 'settings_prefix')
         app = webtest.TestApp(self.config.make_wsgi_app())
         app.get('/v0/__heartbeat__')
         self.mocked().count.assert_any_call('view.heartbeat.GET')
 
     def test_statsd_counts_unknown_urls(self):
-        kinto.core.initialize(self.config, '0.0.1', 'project_name')
+        kinto.core.initialize(self.config, '0.0.1', 'settings_prefix')
         app = webtest.TestApp(self.config.make_wsgi_app())
         app.get('/v0/coucou', status=404)
         self.assertFalse(self.mocked.count.called)
@@ -328,14 +327,14 @@ def test_statsd_counts_unknown_urls(self):
     @mock.patch('kinto.core.utils.hmac_digest')
     def test_statsd_counts_unique_users(self, digest_mocked):
         digest_mocked.return_value = 'mat'
-        kinto.core.initialize(self.config, '0.0.1', 'project_name')
+        kinto.core.initialize(self.config, '0.0.1', 'settings_prefix')
         app = webtest.TestApp(self.config.make_wsgi_app())
         headers = {'Authorization': 'Basic bWF0Og=='}
         app.get('/v0/', headers=headers)
         self.mocked().count.assert_any_call('users', unique='basicauth.mat')
 
     def test_statsd_counts_authentication_types(self):
-        kinto.core.initialize(self.config, '0.0.1', 'project_name')
+        kinto.core.initialize(self.config, '0.0.1', 'settings_prefix')
         app = webtest.TestApp(self.config.make_wsgi_app())
         headers = {'Authorization': 'Basic bWF0Og=='}
         app.get('/v0/', headers=headers)
diff --git a/tests/core/test_listeners.py b/tests/core/test_listeners.py
--- a/tests/core/test_listeners.py
+++ b/tests/core/test_listeners.py
@@ -156,7 +156,7 @@ def test_loading_can_read_configuration_from_environment(self):
 
         config = self.make_app({
             # With real/full initialization, these should not be necessary:
-            'project_name': 'kinto',
+            'settings_prefix': 'kinto',
             'event_listeners': 'kvstore'
         })
 
diff --git a/tests/test_configuration/test.ini b/tests/test_configuration/test.ini
--- a/tests/test_configuration/test.ini
+++ b/tests/test_configuration/test.ini
@@ -141,6 +141,7 @@ kinto.bucket_create_principals = account:admin
 # kinto.version_json_path = ./version.json
 # kinto.error_info_link = https://github.com/kinto/kinto/issues/
 # kinto.project_docs = https://kinto.readthedocs.io
+# kinto.project_name = kinto
 # kinto.project_version =
 # kinto.version_prefix_redirect_enabled = true
 
diff --git a/tests/test_views_hello.py b/tests/test_views_hello.py
--- a/tests/test_views_hello.py
+++ b/tests/test_views_hello.py
@@ -15,6 +15,12 @@ def test_returns_info_about_url_and_version(self):
                          'https://kinto.readthedocs.io/')
         self.assertEqual(response.json['url'], 'http://localhost/v1/')
 
+    def test_project_name_can_be_overriden(self):
+        settings = {'project_name': 'RemoteSettings'}
+        app = self.make_app(settings=settings)
+        resp = app.get('/')
+        self.assertEqual(resp.json['project_name'], 'RemoteSettings')
+
     def test_hides_user_info_if_anonymous(self):
         response = self.app.get('/')
         self.assertNotIn('user', response.json)