Repository: conan-io/conan

Issue:
[question][api] Access to `possible_values` settings after version 2.0.14
### What is your question?

We have a custom command for setting up profiles. Up to version 2.0.13 I had the following code to access to possible value for settings (settings.yml and settings_user.yml):
```python
conan_cache = ClientCache(conan_api.cache_folder)
... = conan_cache.settings.possible_values()
```

I think it broke with PR #14969.

Do you have any hint what's the new way to access this value? I had a look for the code that was in `cache.py` previously and it seems to have moved into `profiles.py` but the whole `_settings()` function there seems to be private.

### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide


Discussion:
Hi @fschoenm 

It is true that we moved and hided the ``settings`` access which was in a bad place (the ``ClientCache`` object probably won't be part of the public API at all).

At the moment it was hidden with the refactor, but I think it is legit that this would be a public API as well, let me discuss with the team what would be the right location for it.
Adding support for the idea that the cache has a helper on the public API (from which settings can be queried) - we use this a lot.

Also, doesn't removal of something from the public API constitute a breaking change? What's the Conan policy for managing/indicating these changes? I assume it won't be to bump the major version as semver suggests
> Adding support for the idea that the cache has a helper on the public API (from which settings can be queried) - we use this a lot.

Sure, in that refactor we actually made the ``global.conf`` access public. The settings will also lie in the same category

> Also, doesn't removal of something from the public API constitute a breaking change? What's the Conan policy for managing/indicating these changes? I assume it won't be to bump the major version as semver suggests

The Python API is explicitly marked as experimental: https://docs.conan.io/2/reference/extensions/python_api.html, and even many of the subAPIs like https://docs.conan.io/2/reference/extensions/python_api/SearchAPI.html are not documented at all yet. We are waiting a bit to make it public, as Conan 2.0 matures and stabilizes a lot of changes have been necessary to APIs, making them stable and publicly documented too early will result in a legacy and outdated interface too early.
We're going to add a property to `conan_api.config` to allow acces to the contents f settings.yml, as well as adding a `conan config` subcommand to be able to also see its contents :)

Resolving patch:
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -1,14 +1,17 @@
 import os
 import platform
 import textwrap
-
+import yaml
 from jinja2 import Environment, FileSystemLoader
 
 from conan import conan_version
+from conans.client.conf import default_settings_yml
 from conan.internal.api import detect_api
 from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
-from conans.model.conf import ConfDefinition
+from conans.errors import ConanException
+from conans.model.conf import ConfDefinition, BUILT_IN_CONFS
+from conans.model.settings import Settings
 from conans.util.files import load, save
 
 
@@ -68,3 +71,47 @@ def global_conf(self):
                     """)
                 save(global_conf_path, default_global_conf)
         return self._new_config
+
+    @property
+    def builtin_confs(self):
+        return BUILT_IN_CONFS
+
+    @property
+    def settings_yml(self):
+        """Returns {setting: [value, ...]} defining all the possible
+                   settings without values"""
+        _home_paths = HomePaths(self.conan_api.cache_folder)
+        settings_path = _home_paths.settings_path
+        if not os.path.exists(settings_path):
+            save(settings_path, default_settings_yml)
+            save(settings_path + ".orig", default_settings_yml)  # stores a copy, to check migrations
+
+        def _load_settings(path):
+            try:
+                return yaml.safe_load(load(path)) or {}
+            except yaml.YAMLError as ye:
+                raise ConanException("Invalid settings.yml format: {}".format(ye))
+
+        settings = _load_settings(settings_path)
+        user_settings_file = _home_paths.settings_path_user
+        if os.path.exists(user_settings_file):
+            settings_user = _load_settings(user_settings_file)
+
+            def appending_recursive_dict_update(d, u):
+                # Not the same behavior as conandata_update, because this append lists
+                for k, v in u.items():
+                    if isinstance(v, list):
+                        current = d.get(k) or []
+                        d[k] = current + [value for value in v if value not in current]
+                    elif isinstance(v, dict):
+                        current = d.get(k) or {}
+                        if isinstance(current, list):  # convert to dict lists
+                            current = {k: None for k in current}
+                        d[k] = appending_recursive_dict_update(current, v)
+                    else:
+                        d[k] = v
+                return d
+
+            appending_recursive_dict_update(settings, settings_user)
+
+        return Settings(settings)
diff --git a/conan/api/subapi/profiles.py b/conan/api/subapi/profiles.py
--- a/conan/api/subapi/profiles.py
+++ b/conan/api/subapi/profiles.py
@@ -1,15 +1,11 @@
 import os
 
-import yaml
-
 from conan.internal.cache.home_paths import HomePaths
-from conans.client.conf import default_settings_yml
+
 from conans.client.loader import load_python_file
 from conans.client.profile_loader import ProfileLoader
 from conans.errors import ConanException, scoped_traceback
 from conans.model.profile import Profile
-from conans.model.settings import Settings
-from conans.util.files import save, load
 
 DEFAULT_PROFILE_NAME = "default"
 
@@ -61,7 +57,7 @@ def get_profiles_from_args(self, args):
 
         global_conf = self._conan_api.config.global_conf
         global_conf.validate()  # TODO: Remove this from here
-        cache_settings = self._settings()
+        cache_settings = self._conan_api.config.settings_yml
         profile_plugin = self._load_profile_plugin()
         cwd = os.getcwd()
         profile_build = self._get_profile(build_profiles, args.settings_build, args.options_build,
@@ -79,7 +75,7 @@ def get_profile(self, profiles, settings=None, options=None, conf=None, cwd=None
         assert isinstance(profiles, list), "Please provide a list of profiles"
         global_conf = self._conan_api.config.global_conf
         global_conf.validate()  # TODO: Remove this from here
-        cache_settings = self._settings()
+        cache_settings = self._conan_api.config.settings_yml
         profile_plugin = self._load_profile_plugin()
 
         profile = self._get_profile(profiles, settings, options, conf, cwd, cache_settings,
@@ -150,44 +146,6 @@ def detect():
         #  good enough at the moment for designing the API interface, but to improve
         return profile
 
-    def _settings(self):
-        """Returns {setting: [value, ...]} defining all the possible
-           settings without values"""
-        settings_path = self._home_paths.settings_path
-        if not os.path.exists(settings_path):
-            save(settings_path, default_settings_yml)
-            save(settings_path + ".orig", default_settings_yml)  # stores a copy, to check migrations
-
-        def _load_settings(path):
-            try:
-                return yaml.safe_load(load(path)) or {}
-            except yaml.YAMLError as ye:
-                raise ConanException("Invalid settings.yml format: {}".format(ye))
-
-        settings = _load_settings(settings_path)
-        user_settings_file = self._home_paths.settings_path_user
-        if os.path.exists(user_settings_file):
-            settings_user = _load_settings(user_settings_file)
-
-            def appending_recursive_dict_update(d, u):
-                # Not the same behavior as conandata_update, because this append lists
-                for k, v in u.items():
-                    if isinstance(v, list):
-                        current = d.get(k) or []
-                        d[k] = current + [value for value in v if value not in current]
-                    elif isinstance(v, dict):
-                        current = d.get(k) or {}
-                        if isinstance(current, list):  # convert to dict lists
-                            current = {k: None for k in current}
-                        d[k] = appending_recursive_dict_update(current, v)
-                    else:
-                        d[k] = v
-                return d
-
-            appending_recursive_dict_update(settings, settings_user)
-
-        return Settings(settings)
-
     def _load_profile_plugin(self):
         profile_plugin = self._home_paths.profile_plugin_path
         if not os.path.exists(profile_plugin):
diff --git a/conan/cli/commands/config.py b/conan/cli/commands/config.py
--- a/conan/cli/commands/config.py
+++ b/conan/cli/commands/config.py
@@ -1,7 +1,6 @@
 from conan.api.output import cli_out_write
 from conan.cli.command import conan_command, conan_subcommand
 from conan.cli.formatters import default_json_formatter
-from conans.model.conf import BUILT_IN_CONFS
 from conans.util.config_parser import get_bool_from_text
 
 
@@ -65,7 +64,7 @@ def config_list(conan_api, parser, subparser, *args):
     Show all the Conan available configurations: core and tools.
     """
     parser.parse_args(*args)
-    return BUILT_IN_CONFS
+    return conan_api.config.builtin_confs
 
 
 @conan_subcommand(formatters={"text": list_text_formatter, "json": default_json_formatter})


Test patch: