示例#1
0
    def test_config_load_multiple_configfiles(self):
        """Test more specific config overrides less specific one,
        on a key by key basis."""

        config_system = u"""
            [alpha]
            endpoint = alpha
            solver = DW_2000Q_1
        """
        config_user = u"""
            [alpha]
            solver = DW_2000Q_2
            [beta]
            endpoint = beta
        """

        with mock.patch("dwave.cloud.config.get_configfile_paths",
                        lambda: ['config_system', 'config_user']):

            # test per-key override
            with mock.patch('dwave.cloud.config.open', create=True) as m:
                m.side_effect=[iterable_mock_open(config_system)(),
                               iterable_mock_open(config_user)()]
                section = load_config(profile='alpha')
                self.assertEqual(section['endpoint'], 'alpha')
                self.assertEqual(section['solver'], 'DW_2000Q_2')

            # test per-section override (section addition)
            with mock.patch('dwave.cloud.config.open', create=True) as m:
                m.side_effect=[iterable_mock_open(config_system)(),
                               iterable_mock_open(config_user)()]
                section = load_config(profile='beta')
                self.assertEqual(section['endpoint'], 'beta')
示例#2
0
    def test_config_file_path_expansion(self):
        """Home dir and env vars are expanded when resolving config path."""

        env = {"var": "val"}
        config_file = "~/path/${var}/to/$var/my.conf"
        expected_path = os.path.expanduser("~/path/val/to/val/my.conf")
        profile = "profile"

        conf_content = """
            [{}]
            valid = yes
        """.format(profile)

        def mock_open(filename, *pa, **kw):
            self.assertEqual(filename, expected_path)
            return iterable_mock_open(conf_content)()

        # config file via kwarg
        with mock.patch.dict(os.environ, env):
            with mock.patch('dwave.cloud.config.open', mock_open) as m:
                conf = load_config(config_file=config_file, profile=profile)
                self.assertEqual(conf['valid'], 'yes')

        # config file via env var
        with mock.patch.dict(os.environ, env, DWAVE_CONFIG_FILE=config_file):
            with mock.patch('dwave.cloud.config.open', mock_open) as m:
                conf = load_config(profile=profile)
                self.assertEqual(conf['valid'], 'yes')
示例#3
0
 def test_config_load_configfile_env_profile_env_key_arg(self):
     """Explicitly provided values should override env/file."""
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files, provided=['myfile'])):
         with mock.patch.dict(os.environ, {'DWAVE_CONFIG_FILE': 'myfile',
                                           'DWAVE_PROFILE': 'alpha'}):
             self.assertEqual(load_config(endpoint='manual')['endpoint'], 'manual')
             self.assertEqual(load_config(token='manual')['token'], 'manual')
             self.assertEqual(load_config(client='manual')['client'], 'manual')
             self.assertEqual(load_config(solver='manual')['solver'], 'manual')
             self.assertEqual(load_config(proxy='manual')['proxy'], 'manual')
示例#4
0
    def test_config_load_skip_configfiles(self):
        with mock.patch("dwave.cloud.config.load_config_from_files",
                        self._load_config_from_files):

            # don't load from file, use arg or env
            self.assertEqual(load_config(config_file=False)['endpoint'], None)
            with mock.patch.dict(os.environ, {'DWAVE_API_ENDPOINT': 'test'}):
                self.assertEqual(load_config(config_file=False)['endpoint'], 'test')

            # specifying a profile doesn't affect outcome
            self.assertEqual(load_config(config_file=False, profile='alpha')['endpoint'], None)
            with mock.patch.dict(os.environ, {'DWAVE_API_ENDPOINT': 'test'}):
                self.assertEqual(load_config(config_file=False, profile='alpha')['endpoint'], 'test')
            with mock.patch.dict(os.environ, {'DWAVE_PROFILE': 'profile'}):
                self.assertEqual(load_config(config_file=False, endpoint='test')['endpoint'], 'test')
示例#5
0
 def test_config_load_configfile_env(self):
     with mock.patch(
             "dwave.cloud.config.load_config_from_files",
             partial(self._load_config_from_files, provided=['myfile'])):
         with mock.patch.dict(os.environ, {'DWAVE_CONFIG_FILE': 'myfile'}):
             self._assert_config_valid(
                 load_config(config_file=None, profile='alpha'))
示例#6
0
    def test_config_load_multiple_explicit_configfiles(self):
        """Test more specific config overrides less specific one,
        on a key by key basis, in a list of explicitly given files."""

        file1 = """
            [alpha]
            endpoint = alpha
            solver = DW_2000Q_1
        """
        file2 = """
            [alpha]
            solver = DW_2000Q_2
        """

        with mock.patch('dwave.cloud.config.open', create=True) as m:
            m.side_effect = [
                iterable_mock_open(file1)(),
                iterable_mock_open(file2)()
            ]
            section = load_config(config_file=['file1', 'file2'],
                                  profile='alpha')
            m.assert_has_calls(
                [mock.call('file1', 'r'),
                 mock.call('file2', 'r')])
            self.assertEqual(section['endpoint'], 'alpha')
            self.assertEqual(section['solver'], 'DW_2000Q_2')
示例#7
0
 def test_config_load_configfile_arg_profile_default(self):
     """Check the right profile is loaded when `profile` specified only in
     [defaults] section.
     """
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files, provided=['myfile'])):
         profile = load_config(config_file='myfile')
         self.assertEqual(profile['solver'], 'c4-sw_sample')
示例#8
0
    def test_config_load_force_autodetection(self):
        with mock.patch("dwave.cloud.config.load_config_from_files",
                        partial(self._load_config_from_files, provided=None)):

            # load from file
            self._assert_config_valid(load_config(config_file=True, profile='alpha'))

            # load from file, even when config_file overridden in env (to path)
            with mock.patch.dict(os.environ, {'DWAVE_CONFIG_FILE': 'nonexisting'}):
                self._assert_config_valid(load_config(config_file=True, profile='alpha'))
                with mock.patch.dict(os.environ, {'DWAVE_PROFILE': 'alpha'}):
                    self._assert_config_valid(load_config(config_file=True))

            # load from file, even when config_file overridden in env (to None)
            with mock.patch.dict(os.environ, {'DWAVE_CONFIG_FILE': ''}):
                self._assert_config_valid(load_config(config_file=True, profile='alpha'))
                with mock.patch.dict(os.environ, {'DWAVE_PROFILE': 'alpha'}):
                    self._assert_config_valid(load_config(config_file=True))
 def test_config_load_configfile_env_profile_env(self):
     with mock.patch(
             "dwave.cloud.config.load_config_from_file",
             partial(self._load_config_from_file, provided='myfile')):
         with mock.patch.dict(os.environ, {
                 'DWAVE_CONFIG_FILE': 'myfile',
                 'DWAVE_PROFILE': 'alpha'
         }):
             self._assert_config_valid(load_config())
示例#10
0
    def test_config_load_env_override(self):
        with mock.patch(
                "dwave.cloud.config.load_config_from_files",
                partial(self._load_config_from_files,
                        data="",
                        provided=['myfile'])):

            with mock.patch.dict(os.environ, {'DWAVE_API_CLIENT': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['client'], 'test')

            with mock.patch.dict(os.environ, {'DWAVE_API_ENDPOINT': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['endpoint'], 'test')

            with mock.patch.dict(os.environ, {'DWAVE_API_TOKEN': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['token'], 'test')

            with mock.patch.dict(os.environ, {'DWAVE_API_SOLVER': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['solver'], 'test')

            with mock.patch.dict(os.environ, {'DWAVE_API_PROXY': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['proxy'], 'test')

            with mock.patch.dict(os.environ, {'DWAVE_API_HEADERS': 'test'}):
                self.assertEqual(
                    load_config(config_file='myfile')['headers'], 'test')
示例#11
0
 def test_config_load__profile_first_section(self):
     """load_config should load the first section for profile, if profile
     is nowhere else specified.
     """
     myconfig = u"""
         [first]
         solver = DW_2000Q_1
     """
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files,
                             provided=None, data=myconfig)):
         profile = load_config()
         self.assertIn('solver', profile)
         self.assertEqual(profile['solver'], 'DW_2000Q_1')
示例#12
0
    def test_update(self, name, extra_opts, inputs):
        config_file = 'dwave.conf'
        profile = 'profile'

        runner = CliRunner(mix_stderr=False)
        with runner.isolated_filesystem():
            # create config
            config_body = '\n'.join(f"{k} = old-{v}"
                                    for k, v in inputs.items())
            with open(config_file, 'w') as fp:
                fp.write(f"[{profile}]\n{config_body}")

            # verify config before update
            with isolated_environ(remove_dwave=True):
                config = load_config(config_file=config_file)
                for var, val in inputs.items():
                    self.assertEqual(config.get(var), f"old-{val}")

            # update config
            result = runner.invoke(cli,
                                   [
                                       'config',
                                       'create',
                                       '-f',
                                       config_file,
                                       '-p',
                                       profile,
                                   ] + extra_opts,
                                   input='\n'.join('' if v is None else v
                                                   for v in inputs.values()))
            self.assertEqual(result.exit_code, 0)

            # verify config updated
            with isolated_environ(remove_dwave=True):
                config = load_config(config_file=config_file)
                for var, val in inputs.items():
                    self.assertEqual(config.get(var), val)
示例#13
0
 def test_config_load__profile_from_defaults(self):
     """load_config should promote [defaults] section to profile, if profile
     is nowhere else specified *and* not even a single non-[defaults] section
     exists.
     """
     myconfig = u"""
         [defaults]
         solver = DW_2000Q_1
     """
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files,
                             provided=None, data=myconfig)):
         profile = load_config()
         self.assertIn('solver', profile)
         self.assertEqual(profile['solver'], 'DW_2000Q_1')
示例#14
0
    def test_configure(self):
        config_file = 'dwave.conf'
        profile = 'profile'
        values = 'endpoint token client solver proxy'.split()

        with mock.patch("six.moves.input", side_effect=values, create=True):
            runner = CliRunner()
            with runner.isolated_filesystem():
                touch(config_file)
                result = runner.invoke(cli, [
                    'configure', '--config-file', config_file, '--profile', profile
                ], input='\n'.join(values))
                self.assertEqual(result.exit_code, 0)

                config = load_config(config_file, profile=profile)
                for val in values:
                    self.assertEqual(config.get(val), val)
示例#15
0
    def test_default_flows(self, name, extra_opts, inputs):
        runner = CliRunner(mix_stderr=False)
        with runner.isolated_filesystem():
            with mock.patch("dwave.cloud.config.get_configfile_paths",
                            lambda: ['dwave.conf']):
                with mock.patch("dwave.cloud.utils.input", side_effect=inputs):
                    result = runner.invoke(
                        cli, ['config', 'create'] + extra_opts,
                        input='\n'.join('' if v is None else v
                                        for v in inputs.values()))
                    self.assertEqual(result.exit_code, 0)

                # load and verify config
                with isolated_environ(remove_dwave=True):
                    config = load_config()
                    for var, val in inputs.items():
                        if val:  # skip empty default confirmations
                            self.assertEqual(config.get(var), val)
示例#16
0
    def test_create(self, name, extra_opts, inputs):
        config_file = 'path/to/dwave.conf'
        profile = 'profile'

        runner = CliRunner(mix_stderr=False)
        with runner.isolated_filesystem():
            result = runner.invoke(cli,
                                   [
                                       'config', 'create', '--config-file',
                                       config_file, '--profile', profile
                                   ] + extra_opts,
                                   input='\n'.join('' if v is None else v
                                                   for v in inputs.values()))
            self.assertEqual(result.exit_code, 0)

            # load and verify config
            with isolated_environ(remove_dwave=True):
                config = load_config(config_file, profile=profile)
                for var, val in inputs.items():
                    self.assertEqual(config.get(var), val)
示例#17
0
    def test_config_create(self):
        config_file = 'dwave.conf'
        profile = 'profile'
        values = 'endpoint token client solver'.split()

        runner = CliRunner()
        with runner.isolated_filesystem():
            # create config file through simulated user input in `dwave configure`
            touch(config_file)
            with mock.patch("dwave.cloud.utils.input", side_effect=values):
                result = runner.invoke(cli, [
                    'config', 'create', '--config-file', config_file, '--profile', profile
                ], input='\n'.join(values))
                self.assertEqual(result.exit_code, 0)

            # load and verify config
            with isolated_environ(remove_dwave=True):
                config = load_config(config_file, profile=profile)
                for val in values:
                    self.assertEqual(config.get(val), val)
示例#18
0
import os
import warnings

from dwave.cloud.config import load_config
from dwave.cloud.exceptions import CanceledFutureError, ConfigFileError

# try to load client config needed for live tests on SAPI web service
try:
    # by default, use `test` profile from `tests/dwave.conf`,
    # with secrets (token) read from env
    default_config_path = os.path.join(os.path.dirname(__file__), 'dwave.conf')
    default_config_profile = 'test'

    # allow manual override of config file and profile used for tests
    test_config_path = os.getenv('DWAVE_CONFIG_FILE', default_config_path)
    test_config_profile = os.getenv('DWAVE_PROFILE', default_config_profile)

    config = load_config(config_file=test_config_path,
                         profile=test_config_profile)

    # ensure config is complete
    for var in 'endpoint token solver'.split():
        if not config[var]:
            raise ValueError("Config incomplete, missing: {!r}".format(var))

except (ConfigFileError, ValueError) as e:
    config = None
    warnings.warn("Skipping live tests due to: {!s}".format(e))
示例#19
0
    def from_config(cls,
                    config_file=None,
                    profile=None,
                    client=None,
                    endpoint=None,
                    token=None,
                    solver=None,
                    proxy=None,
                    legacy_config_fallback=True,
                    **kwargs):
        """Client factory method which loads configuration from file(s),
        process environment variables and explicitly provided values, creating
        and returning the appropriate client instance
        (:class:`dwave.cloud.qpu.Client` or :class:`dwave.cloud.sw.Client`).

        Note:
            For details on config loading from files and environment, please
            see :func:`~dwave.cloud.config.load_config`.

        Args:
            config_file (str/None/False/True, default=None):
                Path to config file. ``None`` for auto-detect, ``False`` to
                skip loading from any file (including auto-detection), and
                ``True`` to force auto-detection, disregarding environment value
                for config file.

            profile (str, default=None):
                Profile name (config file section name). If undefined it is
                taken from ``DWAVE_PROFILE`` environment variable, or config
                file, or first section, or defaults. For details, see
                :func:`~dwave.cloud.config.load_config`.

            client (str, default=None):
                Client class (selected by name) to use for accessing the API.
                Use ``qpu`` to specify the :class:`dwave.cloud.qpu.Client` and
                ``sw`` for :class:`dwave.cloud.sw.Client`.

            endpoint (str, default=None):
                API endpoint URL.

            token (str, default=None):
                API authorization token.

            solver (str, default=None):
                Default solver to use in :meth:`~dwave.cloud.client.Client.get_solver`.
                If undefined, you'll have to explicitly specify the solver name/id
                in all calls to :meth:`~dwave.cloud.client.Client.get_solver`.

            proxy (str, default=None):
                URL for proxy to use in connections to D-Wave API. Can include
                username/password, port, scheme, etc. If undefined, client will
                connect directly to the API (unless you use a system-level proxy).

            legacy_config_fallback (bool, default=True):
                If loading from a ``dwave.conf`` config file fails, try
                loading the ``.dwrc`` legacy config.

            **kwargs:
                All remaining keyword arguments are passed-through as-is to the
                chosen `Client` constructor method.

                A notable custom argument is `permissive_ssl`.

                Note: all user-defined keys from config files are propagated to
                the `Client` constructor too, and can be overridden with these
                keyword arguments.

        Example:
            Create ``dwave.conf`` in your current directory or
            ``~/.config/dwave/dwave.conf``::

                [prod]
                endpoint = https://cloud.dwavesys.com/sapi
                token = DW-123123-secret
                solver = DW_2000Q_1

            Run::

                from dwave.cloud import Client
                with Client.from_config(profile='prod') as client:
                    solver = client.get_solver()
                    computation = solver.sample_ising({}, {})
                    samples = computation.result()

        Raises:
            :exc:`~dwave.cloud.exceptions.ConfigFileReadError`:
                Config file specified or detected could not be opened or read.

            :exc:`~dwave.cloud.exceptions.ConfigFileParseError`:
                Config file parse failed.
        """

        # try loading configuration from a preferred new config subsystem
        # (`./dwave.conf`, `~/.config/dwave/dwave.conf`, etc)
        config = load_config(config_file=config_file,
                             profile=profile,
                             client=client,
                             endpoint=endpoint,
                             token=token,
                             solver=solver,
                             proxy=proxy)

        # fallback to legacy `.dwrc` if key variables missing
        if legacy_config_fallback and (not config.get('token')
                                       or not config.get('endpoint')):
            config = legacy_load_config(profile=profile,
                                        client=client,
                                        endpoint=endpoint,
                                        token=token,
                                        solver=solver,
                                        proxy=proxy)

        # manual override of other (client-custom) arguments
        config.update(kwargs)

        from dwave.cloud import qpu, sw
        _clients = {'qpu': qpu.Client, 'sw': sw.Client}
        _client = config.pop('client', None) or 'qpu'
        return _clients[_client](**config)
示例#20
0
 def test_config_load_configfile_detect_profile_env(self):
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files, provided=None)):
         with mock.patch.dict(os.environ, {'DWAVE_PROFILE': 'alpha'}):
             self._assert_config_valid(load_config())
示例#21
0
 def test_config_load_configfile_detect(self):
     with mock.patch("dwave.cloud.config.load_config_from_files",
                     partial(self._load_config_from_files, provided=None)):
         self._assert_config_valid(
             load_config(config_file=None, profile='alpha'))
示例#22
0
 def test_config_load_configfile_arg(self):
     with mock.patch(
             "dwave.cloud.config.load_config_from_files",
             partial(self._load_config_from_files, provided=['myfile'])):
         self._assert_config_valid(
             load_config(config_file='myfile', profile='alpha'))
示例#23
0
    def from_config(cls,
                    config_file=None,
                    profile=None,
                    client=None,
                    endpoint=None,
                    token=None,
                    solver=None,
                    proxy=None):
        """Client factory method which loads configuration from file(s),
        process environment variables and explicitly provided values, creating
        and returning the appropriate client instance
        (:class:`dwave.cloud.qpu.Client` or :class:`dwave.cloud.sw.Client`).

        Usage example:

            Create ``dwave.conf`` in your current directory or
            ``~/.config/dwave/dwave.conf``::

                [prod]
                endpoint = https://cloud.dwavesys.com/sapi
                token = DW-123123-secret
                solver = DW_2000Q_1

            Run:
                >>> from dwave.cloud import Client
                >>> client = Client.from_config(profile='prod')
                >>> solver = client.get_solver()
                >>> computation = solver.submit({}, {})
                >>> samples = computation.result()

        TODO: describe config loading, new config in broad strokes, refer to
        actual loaders' doc; include examples for config and usage.
        """

        # try loading configuration from a preferred new config subsystem
        # (`./dwave.conf`, `~/.config/dwave/dwave.conf`, etc)
        try:
            config = load_config(config_file=config_file,
                                 profile=profile,
                                 client=client,
                                 endpoint=endpoint,
                                 token=token,
                                 solver=solver,
                                 proxy=proxy)
        except ValueError:
            config = dict(endpoint=endpoint,
                          token=token,
                          solver=solver,
                          proxy=proxy,
                          client=client)

        # and failback to the legacy `.dwrc`
        if config.get('token') is None or config.get('endpoint') is None:
            try:
                _endpoint, _token, _proxy, _solver = legacy_load_config(
                    key=profile,
                    endpoint=endpoint,
                    token=token,
                    solver=solver,
                    proxy=proxy)
                config = dict(endpoint=_endpoint,
                              token=_token,
                              solver=_solver,
                              proxy=_proxy,
                              client=client)
            except (ValueError, IOError):
                pass

        from dwave.cloud import qpu, sw
        _clients = {'qpu': qpu.Client, 'sw': sw.Client}
        _client = config.pop('client') or 'qpu'
        return _clients[_client](**config)
示例#24
0
    def from_config(cls,
                    config_file=None,
                    profile=None,
                    client=None,
                    endpoint=None,
                    token=None,
                    solver=None,
                    proxy=None,
                    legacy_config_fallback=True,
                    **kwargs):
        """Client factory method to instantiate a client instance from configuration.

        Configuration files comply with standard Windows INI-like format,
        parsable with Python's :mod:`configparser`. An optional ``defaults`` section
        provides default key-value pairs for all other sections. User-defined key-value
        pairs (unrecognized keys) are passed through to the client.

        Configuration values can be specified in multiple ways, ranked in the following
        order (with 1 the highest ranked):

        1. Values specified as keyword arguments in :func:`from_config()`
        2. Values specified as environment variables
        3. Values specified in the configuration file

        If the location of the configuration file is not specified, auto-detection
        searches for existing configuration files in the standard directories
        of :func:`get_configfile_paths`.

        If a configuration file explicitly specified, via an argument or
        environment variable, does not exist or is unreadable, loading fails with
        :exc:`~dwave.cloud.exceptions.ConfigFileReadError`. Loading fails
        with :exc:`~dwave.cloud.exceptions.ConfigFileParseError` if the file is
        readable but invalid as a configuration file.

        Similarly, if a profile explicitly specified, via an argument or
        environment variable, is not present in the loaded configuration, loading fails
        with :exc:`ValueError`. Explicit profile selection also fails if the configuration
        file is not explicitly specified, detected on the system, or defined via
        an environment variable.

        Environment variables:

            ``DWAVE_CONFIG_FILE``:
                Configuration file path used if no configuration file is specified.

            ``DWAVE_PROFILE``:
                Name of profile (section) to use if no profile is specified.

            ``DWAVE_API_CLIENT``:
                API client class used if no client is specified. Supported values are
                ``qpu`` or ``sw``.

            ``DWAVE_API_ENDPOINT``:
                API endpoint URL used if no endpoint is specified.

            ``DWAVE_API_TOKEN``:
                API authorization token used if no token is specified.

            ``DWAVE_API_SOLVER``:
                Default solver used if no solver is specified.

            ``DWAVE_API_PROXY``:
                URL for proxy connections to D-Wave API used if no proxy is specified.

        Args:
            config_file (str/[str]/None/False/True, default=None):
                Path to configuration file.

                If ``None``, the value is taken from ``DWAVE_CONFIG_FILE`` environment
                variable if defined. If the environment variable is undefined or empty,
                auto-detection searches for existing configuration files in the standard
                directories of :func:`get_configfile_paths`.

                If ``False``, loading from file is skipped.

                If ``True``, forces auto-detection (regardless of the ``DWAVE_CONFIG_FILE``
                environment variable).

            profile (str, default=None):
                Profile name (name of the profile section in the configuration file).

                If undefined, inferred from ``DWAVE_PROFILE`` environment variable if
                defined. If the environment variable is undefined or empty, a profile is
                selected in the following order:

                1. From the default section if it includes a profile key.
                2. The first section (after the default section).
                3. If no other section is defined besides ``[defaults]``, the defaults
                   section is promoted and selected.

            client (str, default=None):
                Client type used for accessing the API. Supported values are ``qpu``
                for :class:`dwave.cloud.qpu.Client` and ``sw`` for
                :class:`dwave.cloud.sw.Client`.

            endpoint (str, default=None):
                API endpoint URL.

            token (str, default=None):
                API authorization token.

            solver (str, default=None):
                Default :term:`solver` to use in :meth:`~dwave.cloud.client.Client.get_solver`.
                If undefined, :meth:`~dwave.cloud.client.Client.get_solver` will return the
                first solver available.

            proxy (str, default=None):
                URL for proxy to use in connections to D-Wave API. Can include
                username/password, port, scheme, etc. If undefined, client
                uses the system-level proxy, if defined, or connects directly to the API.

            legacy_config_fallback (bool, default=True):
                If True (the default) and loading from a standard D-Wave Cloud Client configuration
                file (``dwave.conf``) fails, tries loading a legacy configuration file (``~/.dwrc``).

        Other Parameters:
            Unrecognized keys (str):
                All unrecognized keys are passed through to the appropriate client class constructor
                as string keyword arguments.

                An explicit key value overrides an identical user-defined key value loaded from a
                configuration file.

        Returns:
            :class:`~dwave.cloud.client.Client` (:class:`dwave.cloud.qpu.Client` or :class:`dwave.cloud.sw.Client`, default=:class:`dwave.cloud.qpu.Client`):
                Appropriate instance of a QPU or software client.

        Raises:
            :exc:`~dwave.cloud.exceptions.ConfigFileReadError`:
                Config file specified or detected could not be opened or read.

            :exc:`~dwave.cloud.exceptions.ConfigFileParseError`:
                Config file parse failed.

        Examples:
            This first example initializes :class:`~dwave.cloud.client.Client` from an
            explicitly specified configuration file, "~/jane/my_path_to_config/my_cloud_conf.conf"::

                [defaults]
                endpoint = https://url.of.some.dwavesystem.com/sapi
                client = qpu
                token = ABC-123456789123456789123456789

                [dw2000]
                solver = EXAMPLE_2000Q_SYSTEM
                token = DEF-987654321987654321987654321

            The example code below creates a client object that connects to a D-Wave QPU,
            using :class:`dwave.cloud.qpu.Client` and ``EXAMPLE_2000Q_SYSTEM`` as a default solver.

            >>> from dwave.cloud import Client
            >>> client = Client.from_config(config_file='~/jane/my_path_to_config/my_cloud_conf.conf')  # doctest: +SKIP
            >>> # code that uses client
            >>> client.close()

            This second example auto-detects a configuration file on the local system following the
            user/system configuration paths of :func:`get_configfile_paths`. It passes through
            to the instantiated client an unrecognized key-value pair my_param=`my_value`.

            >>> from dwave.cloud import Client
            >>> client = Client.from_config(my_param=`my_value`)
            >>> # code that uses client
            >>> client.close()

            This third example instantiates two clients, for managing both QPU and software
            solvers. Common key-value pairs are taken from the defaults section of a shared
            configuration file::

                [defaults]
                endpoint = https://url.of.some.dwavesystem.com/sapi
                client = qpu

                [dw2000A]
                solver = EXAMPLE_2000Q_SYSTEM_A
                token = ABC-123456789123456789123456789

                [sw_solver]
                client = sw
                solver = c4-sw_sample
                endpoint = https://url.of.some.software.resource.com/my_if
                token = DEF-987654321987654321987654321

                [dw2000B]
                solver = EXAMPLE_2000Q_SYSTEM_B
                proxy = http://user:[email protected]:8080/
                token = XYZ-0101010100112341234123412341234

            The example code below creates client objects for two QPU solvers (at the
            same URL but each with its own solver ID and token) and one software solver.

            >>> from dwave.cloud import Client
            >>> client_qpu1 = Client.from_config(profile='dw2000A')    # doctest: +SKIP
            >>> client_qpu1 = Client.from_config(profile='dw2000B')    # doctest: +SKIP
            >>> client_sw1 = Client.from_config(profile='sw_solver')   # doctest: +SKIP
            >>> client_qpu1.default_solver   # doctest: +SKIP
            u'EXAMPLE_2000Q_SYSTEM_A'
            >>> client_qpu2.endpoint   # doctest: +SKIP
            u'https://url.of.some.dwavesystem.com/sapi'
            >>> # code that uses client
            >>> client_qpu1.close() # doctest: +SKIP
            >>> client_qpu2.close() # doctest: +SKIP
            >>> client_sw1.close() # doctest: +SKIP

            This fourth example loads configurations auto-detected in more than one configuration
            file, with the higher priority file (in the current working directory) supplementing
            and overriding values from the lower priority user-local file. After instantiation,
            an endpoint from the default section and client from the profile section is provided
            from the user-local ``/usr/local/share/dwave/dwave.conf`` file::

                [defaults]
                endpoint = https://int.se.dwavesystems.com/sapi

                [dw2000]
                client = qpu
                token = ABC-123456789123456789123456789

            A solver is supplemented from the file in the current working directory, which also
            overrides the token value. ``./dwave.conf`` is the file in the current directory::

                [dw2000]
                solver = EXAMPLE_2000Q_SYSTEM_A
                token = DEF-987654321987654321987654321

            >>> from dwave.cloud import Client
            >>> client = Client.from_config()
            >>> client.default_solver   # doctest: +SKIP
            u'EXAMPLE_2000Q_SYSTEM_A'
            >>> client.endpoint  # doctest: +SKIP
            u'https://int.se.dwavesystems.com/sapi'
            >>> client.token  # doctest: +SKIP
            u'DEF-987654321987654321987654321'
            >>> # code that uses client
            >>> client.close() # doctest: +SKIP

        """

        # try loading configuration from a preferred new config subsystem
        # (`./dwave.conf`, `~/.config/dwave/dwave.conf`, etc)
        config = load_config(config_file=config_file,
                             profile=profile,
                             client=client,
                             endpoint=endpoint,
                             token=token,
                             solver=solver,
                             proxy=proxy)
        _LOGGER.debug("Config loaded: %r", config)

        # fallback to legacy `.dwrc` if key variables missing
        if legacy_config_fallback and (not config.get('token')
                                       or not config.get('endpoint')):
            config = legacy_load_config(profile=profile,
                                        client=client,
                                        endpoint=endpoint,
                                        token=token,
                                        solver=solver,
                                        proxy=proxy)
            _LOGGER.debug("Legacy config loaded: %r", config)

        # manual override of other (client-custom) arguments
        config.update(kwargs)

        from dwave.cloud import qpu, sw
        _clients = {'qpu': qpu.Client, 'sw': sw.Client, 'base': cls}
        _client = config.pop('client', None) or 'base'

        _LOGGER.debug("Final config used for %s.Client(): %r", _client, config)
        return _clients[_client](**config)