Repository: dwavesystems/dwave-cloud-client
Pull request #131 (2018-04-19 23:47:49)

Restructure documentation (overall)
@randomir this branch isn't ready to merge, the PR is just so you know what I'm doing.
You can see what it looks like here: http://cloudclient.readthedocs.io/en/restructure/reference/index.html
but might not be my latest and RtD is not building cli properly. My local build looks like this (I'll look into this later):
![image](https://user-images.githubusercontent.com/34041130/39023730-4fb94498-43f1-11e8-90a6-ab95bb0b2c63.png)



Patch:
diff --git a/.gitignore b/.gitignore
index 7bbc71c..e76e643 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,7 @@ instance/
 
 # Sphinx documentation
 docs/_build/
+docs/reference/generated/
 
 # PyBuilder
 target/
diff --git a/LICENSE b/LICENSE
index ff19950..261eeb9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2018 D-Wave Systems Inc.
+   Copyright [yyyy] [name of copyright owner]
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.rst b/README.rst
index 346e526..1f1edb5 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,6 @@ and scheduling for quantum annealing resources at D-Wave Systems.
 This package aims to provide a minimal Python interface to that layer that
 still captures some reasonable practices for interacting with SAPI.
 
-
 Example
 -------
 
diff --git a/docs/conf.py b/docs/conf.py
index dfc9755..0eca9ea 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -27,11 +27,13 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
+    'sphinx.ext.autosummary',
     'sphinx.ext.autodoc',
     'sphinx.ext.napoleon',
     'sphinx.ext.intersphinx'
 ]
 
+# autodoc_default_flags = ['members']
 autosummary_generate = True
 
 # templates_path = ['_templates']
diff --git a/docs/reference/computation.rst b/docs/reference/computation.rst
index 78eabef..2c222aa 100644
--- a/docs/reference/computation.rst
+++ b/docs/reference/computation.rst
@@ -1,7 +1,40 @@
 .. _computation:
 
+===========
 Computation
-***********
+===========
+
+
+.. currentmodule:: dwave.cloud.computation
+
+.. automodule:: dwave.cloud.computation
+
+Class
+-----
 
 .. autoclass:: dwave.cloud.computation.Future
-   :members:
+
+Methods
+-------
+
+.. autosummary::
+   :toctree: generated
+
+   Future.as_completed
+   Future.cancel
+   Future.done
+   Future.result
+   Future.wait
+   Future.wait_multiple
+
+
+Properties
+----------
+
+.. autosummary::
+   :toctree: generated
+
+   Future.energies
+   Future.occurrences
+   Future.samples
+   Future.timing
diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst
index dd7474c..1abaef8 100644
--- a/docs/reference/configuration.rst
+++ b/docs/reference/configuration.rst
@@ -1,227 +1,36 @@
 .. _configuration:
 
+=============
 Configuration
-*************
+=============
 
-Configuration loading
----------------------
+TODO: Short description of the configuration file
 
-.. automodule:: dwave.cloud.config
-   :members: load_config, legacy_load_config,
-        load_config_from_files, load_profile_from_files,
-        get_configfile_paths
+TODO: fill in...
+1. File
+2. environmental variables
 
+Loading Configuration
+=====================
 
-Configuration examples
-----------------------
+These functions deploy D-Wave cloud client settings from a configuration file.
 
-Direct :class:`~dwave.cloud.client.Client` initialization
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+.. currentmodule:: dwave.cloud.config
 
-The most basic initialization of a new :class:`~dwave.cloud.client.Client`
-instance (like :class:`dwave.cloud.qpu.Client` or
-:class:`dwave.cloud.sw.Client`) is via class constructor arguments. You should
-specify values for at least ``endpoint`` and ``token``::
+.. autosummary::
+   :toctree: generated
 
-    from dwave.cloud.qpu import Client
+   load_config
+   legacy_load_config
+   load_config_from_files
+   load_profile_from_files
 
-    client = Client(endpoint='https://cloud.dwavesys.com/sapi', token='secret')
+Managing Files
+==============
 
-Initializing :class:`~dwave.cloud.client.Client` from explicitly given config file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+These functions manage your D-Wave cloud client configuration files.
 
-Configuration parameters for ``Client`` classes can be read from a config file
-(and process environment variables) via ``Client`` factory method
-:func:`~dwave.cloud.client.Client.from_config`. Configuration loading is
-delegated to :func:`~dwave.cloud.config.load_config`.
+.. autosummary::
+   :toctree: generated
 
-Assuming ``/path/to/config`` file contains::
-
-    [prod]
-    endpoint = https://cloud.dwavesys.com/sapi
-    token = secret
-    client = qpu
-    solver = DW_2000Q_1
-
-The code::
-
-    from dwave.cloud import Client
-
-    client = Client.from_config(config_file='/path/to/config')
-
-will create a client object which will connect to D-Wave production QPU,
-using :class:`dwave.cloud.qpu.Client` and ``DW_2000Q_1`` as a default solver.
-
-Note: in case the config file specified does not exist, or the file is
-unreadable (e.g. no read permission), or format is invalid,
-:exc:`~dwave.cloud.exceptions.ConfigFileReadError` or
-:exc:`~dwave.cloud.exceptions.ConfigFileParseError` will be raised.
-
-Config file auto-detection
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If ``config_file`` parameter to :func:`~dwave.cloud.client.Client.from_config`
-factory method is not specified (or is explicitly set to ``None``), config file
-location is auto-detected. Lookup order of paths examined is described in
-:func:`~dwave.cloud.config.load_config_from_files`.
-
-Assuming (on Linux) the file ``~/.config/dwave/dwave.conf`` contains::
-
-    [prod]
-    endpoint = https://cloud.dwavesys.com/sapi
-    token = secret
-
-The code can be simplified to::
-
-    from dwave.cloud import Client
-
-    client = Client.from_config()
-
-Note: config file read/parse exceptions are not raised in the auto-detect case
-if no suitable file is found. If a file is found, but it's unreadable or
-unparseable, exception are still raised.
-
-Defaults and profiles
-^^^^^^^^^^^^^^^^^^^^^
-
-One config file can contain multiple profiles, each defining a separate
-(endpoint, token, solver, etc.) combination. Since config file conforms to a
-standard Windows INI-style format, profiles are defined by sections like:
-``[profile-a]`` and ``[profile-b]``.
-
-Default values for undefined profile keys are taken from the ``[defaults]``
-section.
-
-For example, assuming ``~/.config/dwave/dwave.conf`` contains::
-
-    [defaults]
-    endpoint = https://cloud.dwavesys.com/sapi
-    client = qpu
-
-    [dw2000]
-    solver = DW_2000Q_1
-    token = ...
-
-    [software]
-    client = sw
-    solver = c4-sw_sample
-    token = ...
-
-    [alpha]
-    endpoint = https://url.to.alpha/api
-    proxy = http://user:pass@myproxy.com:8080/
-    token = ...
-
-We can instantiate a client for D-Wave 2000Q QPU endpoint with::
-
-    from dwave.cloud import Client
-
-    client = Client.from_config(profile='dw2000')
-
-and a client for remote software solver with::
-
-    client = Client.from_config(profile='software')
-
-``alpha`` profile will connect to a pre-release API endpoint via defined HTTP
-proxy server.
-
-Progressive config file override
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-System-wide configuration files are overridden (on a key-by-key,
-section-by-section basis) with user-local config files, and then by current
-directory config files.
-
-For example, if ``/usr/local/share/dwave/dwave.conf`` has::
-
-    [defaults]
-    endpoint = <production>
-    client = qpu
-
-    [prod]
-    token = <token>
-
-and ``~/.config/dwave/dwave.conf`` has::
-
-    [alpha]
-    endpoint = <alpha>
-    token = <token>
-
-and ``./dwave.conf`` has::
-
-    [alpha]
-    proxy = <proxy>
-
-then both profiles can be loaded::
-
-    from dwave.cloud import Client
-
-    production_client = Client.from_config(profile='prod')
-
-    alpha_client = Client.from_config(profile='alpha')
-
-Note: ``alpha`` profile will use alpha endpoint, but also a proxy and a QPU
-client.
-
-Environment variables and explicit argument override
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-All of the basic ``Client`` configuration variables can be specified **via
-environment variables** (see :func:`~dwave.cloud.config.load_config`). Values
-from the environment will **override** values read from config file(s).
-
-In addition, location of the very config file and profile can also be specified
-within process environment.
-
-For example, with ``/tests/test.conf`` containing::
-
-    [defaults]
-    endpoint = <production>
-
-    [prod]
-    token = <token-1>
-
-    [test]
-    token = <token-2>
-
-and environment variables set as::
-
-    DWAVE_CONFIG_FILE=/tests/test.conf
-    DWAVE_PROFILE=test
-    DWAVE_API_SOLVER=DW_2000Q_1
-
-then the standard::
-
-    client = Client.from_config()
-
-will construct a client to connect with ``test`` profile from ``test.conf``
-file, and in addition, to use the ``DW_2000Q_1`` solver.
-
-However, **explicit keyword argument** values in
-:func:`~dwave.cloud.client.Client.from_config` factory will override both file
-and environment values::
-
-    client = Client.from_config(token='token-3', proxy='...')
-
-Custom variables in config files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-All (unrecognized) config file keys are passed-through to the respective
-``Client`` class constructor as string keyword arguments.
-
-This makes config-parsing future-proof and enables passing of (undocumented)
-variables like ``permissive_ssl``. With::
-
-    [defaults]
-    ...
-
-    [testing]
-    endpoint = <testing-endpoint>
-    permissive_ssl = 1
-
-The ``Client`` is constructed with ``Client(endpoint=..., permissive_ssl='1')``.
-
-Managing config files with ``dwave configure``
-----------------------------------------------
-
-TODO: document ``dwave`` CLI.
\ No newline at end of file
+   get_configfile_paths
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index b90f082..401a076 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -8,7 +8,10 @@ Reference Documentation
    :noindex:
 
 .. toctree::
+   :maxdepth: 2
 
+
+   intro
    configuration
    resources
    solver
diff --git a/docs/reference/intro.rst b/docs/reference/intro.rst
new file mode 100644
index 0000000..8eaa648
--- /dev/null
+++ b/docs/reference/intro.rst
@@ -0,0 +1,12 @@
+.. _intro:
+
+============
+Introduction
+============
+
+TODO: some words about solvers, work with SAPI, etc
+
+The Configuration File
+======================
+
+TODO
diff --git a/docs/reference/resources.rst b/docs/reference/resources.rst
index 79aafb0..92378a9 100644
--- a/docs/reference/resources.rst
+++ b/docs/reference/resources.rst
@@ -1,22 +1,77 @@
 .. _resources:
 
+=========
 Resources
-*********
+=========
+
+TODO: short description of term client
 
 Base Client
------------
+===========
+
+.. currentmodule:: dwave.cloud
+
+.. automodule:: dwave.cloud.client
+
+Class
+-----
 
 .. autoclass:: dwave.cloud.client.Client
-   :members:
+
+Methods
+-------
+
+.. currentmodule:: dwave.cloud
+
+.. autosummary::
+   :toctree: generated
+
+   client.Client.close
+   client.Client.from_config
+   client.Client.get_solver
+   client.Client.get_solvers
+   client.Client.is_solver_handled
 
 QPU Client
-----------
+==========
+
+.. currentmodule:: dwave.cloud.qpu
+
+.. automodule:: dwave.cloud.qpu
+
+Class
+-----
 
 .. autoclass:: dwave.cloud.qpu.Client
-   :members:
 
-Software Samplers Client
-------------------------
+Methods
+-------
+
+.. currentmodule:: dwave.cloud
+
+.. autosummary::
+   :toctree: generated
+
+   qpu.Client.is_solver_handled
+
+Software-Samplers Client
+========================
+
+.. currentmodule:: dwave.cloud.sw
+
+.. automodule:: dwave.cloud.sw
+
+Class
+-----
 
 .. autoclass:: dwave.cloud.sw.Client
-   :members:
+
+Methods
+-------
+
+.. currentmodule:: dwave.cloud
+
+.. autosummary::
+   :toctree: generated
+
+   sw.Client.is_solver_handled
diff --git a/docs/reference/solver.rst b/docs/reference/solver.rst
index 405c9d1..068c93c 100644
--- a/docs/reference/solver.rst
+++ b/docs/reference/solver.rst
@@ -1,7 +1,25 @@
 .. _solver:
 
+======
 Solver
-******
+======
 
-.. autoclass:: dwave.cloud.solver.Solver
-   :members:
+.. currentmodule:: dwave.cloud
+
+.. automodule:: dwave.cloud.solver
+
+Class
+-----
+
+.. autoclass:: Solver
+
+Methods
+-------
+
+.. autosummary::
+   :toctree: generated
+
+   Solver.check_problem
+   Solver.retrieve_problem
+   Solver.sample_ising
+   Solver.sample_qubo
diff --git a/dwave/cloud/cli.py b/dwave/cloud/cli.py
index d8d7fef..b4b37d9 100644
--- a/dwave/cloud/cli.py
+++ b/dwave/cloud/cli.py
@@ -1,10 +1,11 @@
 import os
+from functools import wraps
 
 import click
 from timeit import default_timer as timer
 
 from dwave.cloud import Client
-from dwave.cloud.utils import readline_input, click_info_switch
+from dwave.cloud.utils import readline_input
 from dwave.cloud.package_info import __title__, __version__
 from dwave.cloud.exceptions import (
     SolverAuthenticationError, InvalidAPIResponseError, UnsupportedSolverError,
@@ -15,45 +16,61 @@ from dwave.cloud.config import (
     get_configfile_paths)
 
 
-@click.group()
-@click.version_option(prog_name=__title__, version=__version__)
-def cli():
-    """D-Wave cloud tool."""
+def click_info_switch(f):
+    """Decorator to create Click eager info switch option, as described in docs:
+    http://click.pocoo.org/6/options/#callbacks-and-eager-options.
 
+    Takes a no-argument function and abstracts the boilerplate required by
+    Click (value checking, exit on done).
 
-@cli.group()
-def config():
-    """Create, update or inspect cloud client configuration file(s)."""
+    Example:
 
+        @click.option('--my-option', is_flag=True, callback=my_option,
+                    expose_value=False, is_eager=True)
+        def test():
+            pass
 
-@config.command()
-@click.option('--system', is_flag=True,
-              help='List paths of system-wide config files')
-@click.option('--user', is_flag=True,
-              help='List paths of user-local config files')
-@click.option('--local', is_flag=True,
-              help='List paths of local config files')
-@click.option('--include-missing', '-m', is_flag=True,
-              help='List all examined paths, not only existing')
-def ls(system, user, local, include_missing):
-    """List config files detected (and/or paths examined)."""
+        @click_info_switch
+        def my_option()
+            click.echo('some info related to my switch')
+    """
 
-    # default action is to list *all* auto-detected files
-    if not (system or user or local):
-        system = user = local = True
+    @wraps(f)
+    def wrapped(ctx, param, value):
+        if not value or ctx.resilient_parsing:
+            return
+        f()
+        ctx.exit()
+    return wrapped
 
-    for path in get_configfile_paths(system=system, user=user, local=local,
-                                     only_existing=not include_missing):
+
+@click_info_switch
+def list_config_files():
+    for path in get_configfile_paths():
         click.echo(path)
 
+@click_info_switch
+def list_system_config():
+    for path in get_configfile_paths(user=False, local=False, only_existing=False):
+        click.echo(path)
 
-@config.command()
-@click.option('--config-file', '-c', default=None,
-              type=click.Path(exists=True, dir_okay=False), help='Config file path')
-@click.option('--profile', '-p', default=None,
-              help='Connection profile name (config section name)')
-def inspect(config_file, profile):
-    """Inspect existing config/profile."""
+@click_info_switch
+def list_user_config():
+    for path in get_configfile_paths(system=False, local=False, only_existing=False):
+        click.echo(path)
+
+@click_info_switch
+def list_local_config():
+    for path in get_configfile_paths(system=False, user=False, only_existing=False):
+        click.echo(path)
+
+
+def inspect_config(ctx, param, value):
+    if not value or ctx.resilient_parsing:
+        return
+
+    config_file = ctx.params.get('config_file')
+    profile = ctx.params.get('profile')
 
     try:
         section = load_profile_from_files(
@@ -68,13 +85,36 @@ def inspect(config_file, profile):
     except (ValueError, ConfigFileReadError, ConfigFileParseError) as e:
         click.echo(e)
 
+    ctx.exit()
 
-@config.command()
-@click.option('--config-file', '-c', default=None,
-              type=click.Path(exists=False, dir_okay=False), help='Config file path')
-@click.option('--profile', '-p', default=None,
+
+@click.group()
+@click.version_option(prog_name=__title__, version=__version__)
+def cli():
+    """D-Wave cloud tool."""
+
+
+@cli.command()
+@click.option('--config-file', '-c', default=None, is_eager=True,
+              type=click.Path(exists=False, dir_okay=False),
+              help='Config file path')
+@click.option('--profile', '-p', default=None, is_eager=True,
               help='Connection profile name (config section name)')
-def create(config_file, profile):
+@click.option('--inspect', is_flag=True, expose_value=False, callback=inspect_config,
+              help='Only inspect existing config/profile (no update)')
+@click.option('--list-config-files', is_flag=True, callback=list_config_files,
+              expose_value=False, is_eager=True,
+              help='List paths of all config files detected on this system')
+@click.option('--list-system-paths', is_flag=True, callback=list_system_config,
+              expose_value=False, is_eager=True,
+              help='List paths of system-wide config files examined')
+@click.option('--list-user-paths', is_flag=True, callback=list_user_config,
+              expose_value=False, is_eager=True,
+              help='List paths of user-local config files examined')
+@click.option('--list-local-paths', is_flag=True, callback=list_local_config,
+              expose_value=False, is_eager=True,
+              help='List paths of local config files examined')
+def configure(config_file, profile):
     """Create and/or update cloud client configuration file."""
 
     # determine the config file path
@@ -150,10 +190,9 @@ def create(config_file, profile):
 
 
 @cli.command()
-@click.option('--config-file', '-c', default=None,
-              type=click.Path(exists=True, dir_okay=False), help='Config file path')
-@click.option('--profile', '-p', default=None,
-              help='Connection profile name (config section name)')
+@click.option('--config-file', '-c', default=None, help='Config file path',
+              type=click.Path(exists=True, dir_okay=False))
+@click.option('--profile', '-p', default=None, help='Connection profile name')
 def ping(config_file, profile):
     """Ping the QPU by submitting a single-qubit problem."""
 
@@ -196,48 +235,10 @@ def ping(config_file, profile):
     t2 = timer()
 
     click.echo("\nWall clock time:")
-    click.echo(" * Solver definition fetch: {:.3f} ms".format((t1-t0)*1000.0))
+    click.echo(" * Solver definition fetch web request: {:.3f} ms".format((t1-t0)*1000.0))
     click.echo(" * Problem submit and results fetch: {:.3f} ms".format((t2-t1)*1000.0))
     click.echo(" * Total: {:.3f} ms".format((t2-t0)*1000.0))
     click.echo("\nQPU timing:")
     for component, duration in timing.items():
         click.echo(" * {} = {} us".format(component, duration))
     return 0
-
-
-@cli.command()
-@click.option('--config-file', '-c', default=None,
-              type=click.Path(exists=True, dir_okay=False), help='Config file path')
-@click.option('--profile', '-p', default=None, help='Connection profile name')
-@click.option('--id', default=None, help='Solver ID/name')
-def solvers(config_file, profile, id):
-    """Get solver details.
-
-    Unless solver name/id specified, fetch and display details for
-    all solvers available on configured endpoint.
-    """
-
-    def trunc(s, maxlen=60):
-        s = str(s)
-        return s[:(maxlen-3)]+'...' if len(s) > maxlen else s
-
-    with Client.from_config(config_file=config_file, profile=profile) as client:
-        solvers = client.get_solvers().values()
-
-        if id:
-            solvers = filter(lambda s: s.id == id, solvers)
-            if not solvers:
-                click.echo("Solver {} not found.".format(id))
-                return 1
-
-        # ~YAML output
-        for solver in solvers:
-            click.echo("Solver: {}".format(solver.id))
-            click.echo("  Parameters:")
-            for param, desc in sorted(solver.parameters.items()):
-                click.echo("    {}: {}".format(param, trunc(desc) if desc else '?'))
-            solver.properties.pop('parameters', None)
-            click.echo("  Properties:")
-            for k,v in sorted(solver.properties.items()):
-                click.echo("    {}: {}".format(k, trunc(v)))
-            click.echo()
diff --git a/dwave/cloud/client.py b/dwave/cloud/client.py
index 4dcbc3f..3526da4 100644
--- a/dwave/cloud/client.py
+++ b/dwave/cloud/client.py
@@ -1,3 +1,10 @@
+"""
+Base client for all D-Wave API clients.
+
+Used by QPU and software sampler classes.
+
+"""
+
 from __future__ import division, absolute_import
 
 import sys
@@ -23,7 +30,7 @@ _LOGGER = logging.getLogger(__name__)
 
 class Client(object):
     """
-    Base client for all D-Wave API clients.
+    Base client class for all D-Wave API clients.
 
     Implements workers (and handles thread pools) for problem submittal, task
     cancellation, problem status polling and results downloading.
@@ -43,6 +50,35 @@ class Client(object):
 
         permissive_ssl (bool, default=False):
             Disables SSL verification.
+
+    Examples:
+        This example show direct :class:`~dwave.cloud.client.Client` initializiation.
+
+        The most basic initialization of a new :class:`~dwave.cloud.client.Client`
+        instance (like :class:`dwave.cloud.qpu.Client` or
+        :class:`dwave.cloud.sw.Client`) is via class constructor arguments. You should
+        specify values for at least ``endpoint`` and ``token``::
+
+        >>> from dwave.cloud.qpu import Client
+        >>> client = Client(endpoint='https://cloud.dwavesys.com/sapi', token='secret')
+
+        This example shows unrecognized configuration file keys being passed through.
+
+        All (unrecognized) config file keys are passed-through to the respective
+        ``Client`` class constructor as string keyword arguments.
+
+        This makes config-parsing future-proof and enables passing of (undocumented)
+        variables like ``permissive_ssl``. With::
+
+            [defaults]
+            ...
+
+            [testing]
+            endpoint = <testing-endpoint>
+            permissive_ssl = 1
+
+        The ``Client`` is constructed with ``Client(endpoint=..., permissive_ssl='1')``.
+
     """
 
     # The status flags that a problem can have
@@ -129,8 +165,173 @@ class Client(object):
                 the `Client` constructor too, and can be overridden with these
                 keyword arguments.
 
-        Example:
-            Create ``dwave.conf`` in your current directory or
+        Examples:
+            (1) This example initializes :class:`~dwave.cloud.client.Client` from an
+            explicitly given configuration file.
+
+            Configuration parameters for ``Client`` classes can be read from a config file
+            (and process environment variables) via ``Client`` factory method
+            :func:`~dwave.cloud.client.Client.from_config`. Configuration loading is
+            delegated to :func:`~dwave.cloud.config.load_config`.
+
+            Assuming ``/path/to/config`` file contains::
+
+                [prod]
+                endpoint = https://cloud.dwavesys.com/sapi
+                token = secret
+                client = qpu
+                solver = DW_2000Q_1
+
+            >>> from dwave.cloud import Client
+            >>> client = Client.from_config(config_file='/path/to/config')
+
+            The above creates a client object which will connect to D-Wave production QPU,
+            using :class:`dwave.cloud.qpu.Client` and ``DW_2000Q_1`` as a default solver.
+
+            Note: in case the config file specified does not exist, or the file is
+            unreadable (e.g. no read permission), or format is invalid,
+            :exc:`~dwave.cloud.exceptions.ConfigFileReadError` or
+            :exc:`~dwave.cloud.exceptions.ConfigFileParseError` will be raised.
+
+            (2) This example demonstrates auto-detection of a configuration file.
+
+            If ``config_file`` parameter to :func:`~dwave.cloud.client.Client.from_config`
+            factory method is not specified (or is explicitly set to ``None``), config file
+            location is auto-detected. Lookup order of paths examined is described in
+            :func:`~dwave.cloud.config.load_config_from_files`.
+
+            Assuming (on Linux) the file ``~/.config/dwave/dwave.conf`` contains::
+
+                [prod]
+                endpoint = https://cloud.dwavesys.com/sapi
+                token = secret
+
+            >>> from dwave.cloud import Client
+            >>> client = Client.from_config()
+
+            Note: config file read/parse exceptions are not raised in the auto-detect case
+            if no suitable file is found. If a file is found, but it's unreadable or
+            unparseable, exception are still raised.
+
+            (3) This example demonstrates defaults and profiles.
+            One config file can contain multiple profiles, each defining a separate
+            (endpoint, token, solver, etc.) combination. Since config file conforms to a
+            standard Windows INI-style format, profiles are defined by sections like:
+            ``[profile-a]`` and ``[profile-b]``.
+
+            Default values for undefined profile keys are taken from the ``[defaults]``
+            section.
+
+            For example, assuming ``~/.config/dwave/dwave.conf`` contains::
+
+                [defaults]
+                endpoint = https://cloud.dwavesys.com/sapi
+                client = qpu
+
+                [dw2000]
+                solver = DW_2000Q_1
+                token = ...
+
+                [software]
+                client = sw
+                solver = c4-sw_sample
+                token = ...
+
+                [alpha]
+                endpoint = https://url.to.alpha/api
+                proxy = http://user:pass@myproxy.com:8080/
+                token = ...
+
+            We can instantiate a client for D-Wave 2000Q QPU endpoint with
+
+            >>> from dwave.cloud import Client
+            >>> client = Client.from_config(profile='dw2000')
+
+            and a client for remote software solver with::
+
+            >>> client = Client.from_config(profile='software')
+
+            ``alpha`` profile will connect to a pre-release API endpoint via defined HTTP
+            proxy server.
+
+            (4) This example demonstrates progressive config file override
+
+            System-wide configuration files are overridden (on a key-by-key,
+            section-by-section basis) with user-local config files, and then by current
+            directory config files.
+
+            For example, if ``/usr/local/share/dwave/dwave.conf`` has::
+
+                [defaults]
+                endpoint = <production>
+                client = qpu
+
+                [prod]
+                token = <token>
+
+            and ``~/.config/dwave/dwave.conf`` has::
+
+                [alpha]
+                endpoint = <alpha>
+                token = <token>
+
+            and ``./dwave.conf`` has::
+
+                [alpha]
+                proxy = <proxy>
+
+            then both profiles can be loaded::
+
+                from dwave.cloud import Client
+
+                production_client = Client.from_config(profile='prod')
+
+                alpha_client = Client.from_config(profile='alpha')
+
+            Note: ``alpha`` profile will use alpha endpoint, but also a proxy and a QPU
+            client.
+
+            (5) This example demonstrates Environment variables and explicit argument override.
+
+            All of the basic ``Client`` configuration variables can be specified **via
+            environment variables** (see :func:`~dwave.cloud.config.load_config`). Values
+            from the environment will **override** values read from config file(s).
+
+            In addition, location of the very config file and profile can also be specified
+            within process environment.
+
+            For example, with ``/tests/test.conf`` containing::
+
+                [defaults]
+                endpoint = <production>
+
+                [prod]
+                token = <token-1>
+
+                [test]
+                token = <token-2>
+
+            and environment variables set as::
+
+                DWAVE_CONFIG_FILE=/tests/test.conf
+                DWAVE_PROFILE=test
+                DWAVE_API_SOLVER=DW_2000Q_1
+
+            then the standard::
+
+                client = Client.from_config()
+
+            will construct a client to connect with ``test`` profile from ``test.conf``
+            file, and in addition, to use the ``DW_2000Q_1`` solver.
+
+            However, **explicit keyword argument** values in
+            :func:`~dwave.cloud.client.Client.from_config` factory will override both file
+            and environment values::
+
+                client = Client.from_config(token='token-3', proxy='...')
+
+
+            (6) For another example, create ``dwave.conf`` in your current directory or
             ``~/.config/dwave/dwave.conf``::
 
                 [prod]
diff --git a/dwave/cloud/computation.py b/dwave/cloud/computation.py
index 3b35ac1..1fc6bbd 100644
--- a/dwave/cloud/computation.py
+++ b/dwave/cloud/computation.py
@@ -1,3 +1,7 @@
+"""
+TODO: short description about computation
+"""
+
 from __future__ import division, absolute_import
 
 import threading
diff --git a/dwave/cloud/package_info.py b/dwave/cloud/package_info.py
index 82749d4..f962c89 100644
--- a/dwave/cloud/package_info.py
+++ b/dwave/cloud/package_info.py
@@ -1,6 +1,6 @@
 __packagename__ = 'dwave-cloud-client'
 __title__ = 'D-Wave Cloud Client'
-__version__ = '0.3.5'
+__version__ = '0.3.4'
 __author__ = 'D-Wave Systems Inc.'
 __description__ = 'A minimal client for interacting with D-Wave cloud resources.'
 __url__ = 'https://github.com/dwavesystems/dwave-cloud-client'
diff --git a/dwave/cloud/solver.py b/dwave/cloud/solver.py
index b164fa7..a2501ff 100644
--- a/dwave/cloud/solver.py
+++ b/dwave/cloud/solver.py
@@ -1,3 +1,7 @@
+"""
+TODO: some explanation of solvers
+"""
+
 from __future__ import division, absolute_import
 
 import json
diff --git a/dwave/cloud/utils.py b/dwave/cloud/utils.py
index cb0f3f2..9789b42 100644
--- a/dwave/cloud/utils.py
+++ b/dwave/cloud/utils.py
@@ -1,7 +1,5 @@
 from __future__ import division, absolute_import
 
-from functools import wraps
-
 import six
 import readline
 
@@ -65,31 +63,3 @@ def readline_input(prompt, prefill=''):
         return six.moves.input(prompt)
     finally:
         readline.set_startup_hook()
-
-
-def click_info_switch(f):
-    """Decorator to create eager Click info switch option, as described in:
-    http://click.pocoo.org/6/options/#callbacks-and-eager-options.
-
-    Takes a no-argument function and abstracts the boilerplate required by
-    Click (value checking, exit on done).
-
-    Example:
-
-        @click.option('--my-option', is_flag=True, callback=my_option,
-                    expose_value=False, is_eager=True)
-        def test():
-            pass
-
-        @click_info_switch
-        def my_option()
-            click.echo('some info related to my switch')
-    """
-
-    @wraps(f)
-    def wrapped(ctx, param, value):
-        if not value or ctx.resilient_parsing:
-            return
-        f()
-        ctx.exit()
-    return wrapped


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 3ffc3eb..0ada056 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -19,7 +19,7 @@ def touch(path):
 
 class TestCli(unittest.TestCase):
 
-    def test_config_create(self):
+    def test_configure(self):
         config_file = 'dwave.conf'
         profile = 'profile'
         values = 'endpoint token client solver proxy'.split()
@@ -30,7 +30,7 @@ class TestCli(unittest.TestCase):
             touch(config_file)
             with mock.patch("six.moves.input", side_effect=values, create=True):
                 result = runner.invoke(cli, [
-                    'config', 'create', '--config-file', config_file, '--profile', profile
+                    'configure', '--config-file', config_file, '--profile', profile
                 ], input='\n'.join(values))
                 self.assertEqual(result.exit_code, 0)
 
@@ -40,7 +40,7 @@ class TestCli(unittest.TestCase):
                 for val in values:
                     self.assertEqual(config.get(val), val)
 
-    def test_config_ls(self):
+    def test_configure_list_config(self):
         runner = CliRunner()
         with runner.isolated_filesystem():
             touch('dwave.conf')
@@ -49,65 +49,30 @@ class TestCli(unittest.TestCase):
                 with mock.patch('dwave.cloud.config.homebase.user_config_dir',
                                 lambda **kw: '/user'):
                     with mock.patch('os.path.exists', lambda *x: True):
-                        # test listing of all auto-detected config files
+                        # test --list-config-files
                         result = runner.invoke(cli, [
-                            'config', 'ls'
+                            'configure', '--list-config-files'
                         ])
                         self.assertEqual(result.output.strip(), '\n'.join([
                             '/system1/dwave.conf', '/system2/dwave.conf',
                             '/user/dwave.conf', './dwave.conf']))
 
-                        # test --system
+                        # test --list-system-paths
                         result = runner.invoke(cli, [
-                            'config', 'ls', '--system'
+                            'configure', '--list-system-paths'
                         ])
                         self.assertEqual(result.output.strip(), '\n'.join([
                             '/system1/dwave.conf', '/system2/dwave.conf']))
 
-                        # test --user
+                        # test --list-user-paths
                         result = runner.invoke(cli, [
-                            'config', 'ls', '--user'
+                            'configure', '--list-user-paths'
                         ])
                         self.assertEqual(result.output.strip(), '/user/dwave.conf')
 
-                        # test --local
+                        # test --list-local-paths
                         result = runner.invoke(cli, [
-                            'config', 'ls', '--local'
-                        ])
-                        self.assertEqual(result.output.strip(), './dwave.conf')
-
-                    # test --include-missing (none of the examined paths exist)
-                    with mock.patch('os.path.exists', lambda *x: False):
-                        # test listing of all examined paths
-                        result = runner.invoke(cli, [
-                            'config', 'ls', '--include-missing'
-                        ])
-                        self.assertEqual(result.output.strip(), '\n'.join([
-                            '/system1/dwave.conf', '/system2/dwave.conf',
-                            '/user/dwave.conf', './dwave.conf']))
-
-                        # test none exists
-                        result = runner.invoke(cli, [
-                            'config', 'ls', '--system', '--user', '--local'
-                        ])
-                        self.assertEqual(result.output.strip(), '')
-
-                        # test --system
-                        result = runner.invoke(cli, [
-                            'config', 'ls', '--system', '--include-missing'
-                        ])
-                        self.assertEqual(result.output.strip(), '\n'.join([
-                            '/system1/dwave.conf', '/system2/dwave.conf']))
-
-                        # test --user
-                        result = runner.invoke(cli, [
-                            'config', 'ls', '--user', '--include-missing'
-                        ])
-                        self.assertEqual(result.output.strip(), '/user/dwave.conf')
-
-                        # test --local
-                        result = runner.invoke(cli, [
-                            'config', 'ls', '--local', '--include-missing'
+                            'configure', '--list-local-paths'
                         ])
                         self.assertEqual(result.output.strip(), './dwave.conf')
 
@@ -128,19 +93,28 @@ class TestCli(unittest.TestCase):
             with mock.patch('dwave.cloud.config.get_configfile_paths',
                             lambda **kw: [config_file]):
                 result = runner.invoke(cli, [
-                    'config', 'inspect'
+                    'configure', '--inspect'
                 ])
                 self.assertIn('endpoint = 2', result.output)
 
             # test explicit config
             result = runner.invoke(cli, [
-                'config', 'inspect', '--config-file', config_file
+                'configure', '--inspect', '--config-file', config_file
             ])
             self.assertIn('endpoint = 2', result.output)
 
             # test explicit profile
             result = runner.invoke(cli, [
-                'config', 'inspect', '--config-file', config_file, '--profile', 'b'
+                'configure', '--inspect', '--config-file', config_file,
+                '--profile', 'b'
+            ])
+            self.assertIn('endpoint = 1', result.output)
+            self.assertIn('token = 3', result.output)
+
+            # test eagerness of config-file ane profile
+            result = runner.invoke(cli, [
+                'configure', '--config-file', config_file,
+                '--profile', 'b', '--inspect'
             ])
             self.assertIn('endpoint = 1', result.output)
             self.assertIn('token = 3', result.output)