Repository: dwavesystems/dwave-cloud-client
Pull request #479 (2021-08-20 12:32:43)

Consolidate client subclasses
Porting #461 (merged to `shared-threadpools` branch) over to master/trunk.

Rebasing and adding release notes.


Patch:
diff --git a/dwave/cloud/__init__.py b/dwave/cloud/__init__.py
index 96bc824..c943cb5 100644
--- a/dwave/cloud/__init__.py
+++ b/dwave/cloud/__init__.py
@@ -13,7 +13,9 @@
 # limitations under the License.
 
 import os
+import sys
 import logging
+import importlib
 
 from dwave.cloud.client import Client
 from dwave.cloud.solver import Solver
@@ -52,3 +54,13 @@ def _apply_loglevel_from_env(logger):
     set_loglevel(logger, os.getenv('DWAVE_LOG_LEVEL'))
 
 _apply_loglevel_from_env(logger)
+
+
+# alias dwave.cloud.client.{qpu,sw,hybrid} as dwave.cloud.*
+def _alias_old_client_submodules():
+    for name in ('qpu', 'sw', 'hybrid'):
+        # note: create both module and local attribute
+        globals()[name] = sys.modules['dwave.cloud.{}'.format(name)] = \
+            importlib.import_module('dwave.cloud.client.{}'.format(name))
+
+_alias_old_client_submodules()
diff --git a/dwave/cloud/client/__init__.py b/dwave/cloud/client/__init__.py
new file mode 100644
index 0000000..cb77f13
--- /dev/null
+++ b/dwave/cloud/client/__init__.py
@@ -0,0 +1,22 @@
+# Copyright 2021 D-Wave Systems Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from dwave.cloud.client.base import Client
+from dwave.cloud.package_info import __packagename__, __version__
+
+__all__ = ['Client']
+
+logger = logging.getLogger(__name__)
diff --git a/dwave/cloud/client.py b/dwave/cloud/client/base.py
similarity index 98%
rename from dwave/cloud/client.py
rename to dwave/cloud/client/base.py
index af6460f..57aaee4 100644
--- a/dwave/cloud/client.py
+++ b/dwave/cloud/client/base.py
@@ -57,7 +57,7 @@ import codecs
 import concurrent.futures
 
 from itertools import chain
-from functools import partial, wraps
+from functools import partial, wraps, lru_cache
 from collections import abc, namedtuple, OrderedDict
 from concurrent.futures import ThreadPoolExecutor
 
@@ -331,7 +331,7 @@ class Client(object):
         config.update({k: v for k, v in kwargs.items() if v is not None})
         logger.debug("Code config loaded: %r", config)
 
-        from dwave.cloud import qpu, sw, hybrid
+        from dwave.cloud.client import qpu, sw, hybrid
         _clients = {
             'base': cls,
             'qpu': qpu.Client,
@@ -521,6 +521,24 @@ class Client(object):
         self._encode_problem_executor = \
             ThreadPoolExecutor(self._ENCODE_PROBLEM_THREAD_COUNT)
 
+    # note: @cached_property available only in py38+
+    @property
+    @lru_cache(maxsize=None)
+    def _user_agent(self):
+        """User-Agent string for this client instance, as returned by
+        :meth:`~dwave.cloud.utils.user_agent`, computed on first access and
+        cached for the lifespan of the client.
+
+        Note:
+            The only tags that might change are platform tags, as returned by
+            ``dwave.common.platform.tags`` entry points, and `platform.platform()`
+            (like linux kernel version). Assuming OS/machine won't change during
+            client's lifespan, and typical platform tags defined via entry points
+            depend on process environments (which rarely change), it's pretty safe
+            to always use the per-instance cached user agent.
+        """
+        return user_agent(__packagename__, __version__)
+
     def create_session(self):
         """Create a new requests session based on client's (self) params.
 
@@ -559,7 +577,7 @@ class Client(object):
             TimeoutingHTTPAdapter(timeout=self.request_timeout,
                                   max_retries=get_retry_conf()))
 
-        session.headers.update({'User-Agent': user_agent(__packagename__, __version__)})
+        session.headers.update({'User-Agent': self._user_agent})
         if self.headers:
             session.headers.update(self.headers)
         if self.token:
diff --git a/dwave/cloud/hybrid.py b/dwave/cloud/client/hybrid.py
similarity index 100%
rename from dwave/cloud/hybrid.py
rename to dwave/cloud/client/hybrid.py
diff --git a/dwave/cloud/qpu.py b/dwave/cloud/client/qpu.py
similarity index 100%
rename from dwave/cloud/qpu.py
rename to dwave/cloud/client/qpu.py
diff --git a/dwave/cloud/sw.py b/dwave/cloud/client/sw.py
similarity index 100%
rename from dwave/cloud/sw.py
rename to dwave/cloud/client/sw.py
diff --git a/dwave/cloud/concurrency.py b/dwave/cloud/concurrency.py
index 2ab223a..6f0b924 100644
--- a/dwave/cloud/concurrency.py
+++ b/dwave/cloud/concurrency.py
@@ -62,7 +62,7 @@ class _PrioritizedWorkItem(_PriorityOrderedItem):
         else:
             priority = item.kwargs.pop('priority', sys.maxsize)
 
-        super(_PrioritizedWorkItem, self).__init__(item, priority)
+        super().__init__(item, priority)
 
 
 class _PrioritizingQueue(queue.PriorityQueue):
@@ -78,10 +78,10 @@ class _PrioritizingQueue(queue.PriorityQueue):
         else:
             item = _PriorityOrderedItem(item)
 
-        super(queue.PriorityQueue, self).put(item, *args, **kwargs)
+        super().put(item, *args, **kwargs)
 
     def get(self, *args, **kwargs):
-        prioritized_item = super(queue.PriorityQueue, self).get(*args, **kwargs)
+        prioritized_item = super().get(*args, **kwargs)
         return prioritized_item.item
 
 
@@ -102,7 +102,7 @@ class PriorityThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor):
     """
 
     def __init__(self, *args, **kwargs):
-        super(PriorityThreadPoolExecutor, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self._work_queue = _PrioritizingQueue()
 
 
@@ -115,7 +115,7 @@ class Present(concurrent.futures.Future):
     """
 
     def __init__(self, result=None, exception=None):
-        super(Present, self).__init__()
+        super().__init__()
         if result is not None:
             self.set_result(result)
         elif exception is not None:
diff --git a/dwave/cloud/exceptions.py b/dwave/cloud/exceptions.py
index db47e6c..5cac699 100644
--- a/dwave/cloud/exceptions.py
+++ b/dwave/cloud/exceptions.py
@@ -30,10 +30,10 @@ class SAPIError(Exception):
         self.error_code = kwargs.pop('error_code', None)
         if len(args) < 1:
             args = (self.error_msg, )
-        super(SAPIError, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def __str__(self):
-        return super(SAPIError, self).__str__() or self.error_msg or ''
+        return super().__str__() or self.error_msg or ''
 
 class ResourceAuthenticationError(SAPIError):
     """Access to resource not authorized."""
@@ -65,7 +65,7 @@ class SolverAuthenticationError(ResourceAuthenticationError, SolverError):
     """An exception raised when there is an authentication error."""
 
     def __init__(self, *args, **kwargs):
-        super(SolverAuthenticationError, self).__init__("Invalid token or access denied.", *args, **kwargs)
+        super().__init__("Invalid token or access denied.", *args, **kwargs)
 
 class UnsupportedSolverError(SolverError):
     """The solver we received from the API is not supported by the client."""
@@ -88,7 +88,7 @@ class CanceledFutureError(Exception):
     """An exception raised when code tries to read from a canceled future."""
 
     def __init__(self):
-        super(CanceledFutureError, self).__init__("An error occurred reading results from a canceled request")
+        super().__init__("An error occurred reading results from a canceled request")
 
 
 class InvalidAPIResponseError(SAPIError):
diff --git a/dwave/cloud/solver.py b/dwave/cloud/solver.py
index fbaf0a8..dbd3880 100644
--- a/dwave/cloud/solver.py
+++ b/dwave/cloud/solver.py
@@ -635,7 +635,7 @@ class StructuredSolver(BaseSolver):
     _handled_encoding_formats = {"qp"}
 
     def __init__(self, *args, **kwargs):
-        super(StructuredSolver, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         # The exact sequence of nodes/edges is used in encoding problems and must be preserved
         try:
diff --git a/dwave/cloud/upload.py b/dwave/cloud/upload.py
index 00794ec..6426728 100644
--- a/dwave/cloud/upload.py
+++ b/dwave/cloud/upload.py
@@ -259,7 +259,7 @@ class FileView(io.RawIOBase):
     """
 
     def __init__(self, raw):
-        super(FileView, self).__init__()
+        super().__init__()
         self._raw = raw
         self._pos = 0
         self._offset = 0
diff --git a/dwave/cloud/utils.py b/dwave/cloud/utils.py
index aa4b7a0..301c60f 100644
--- a/dwave/cloud/utils.py
+++ b/dwave/cloud/utils.py
@@ -242,12 +242,12 @@ class TimeoutingHTTPAdapter(requests.adapters.HTTPAdapter):
 
     def __init__(self, timeout=None, *args, **kwargs):
         self.timeout = timeout
-        super(TimeoutingHTTPAdapter, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def send(self, *args, **kwargs):
         # can't use setdefault because caller always sets timeout kwarg
         kwargs['timeout'] = self.timeout
-        return super(TimeoutingHTTPAdapter, self).send(*args, **kwargs)
+        return super().send(*args, **kwargs)
 
 
 # Note: BaseUrlSession is taken from https://github.com/requests/toolbelt under
@@ -263,14 +263,12 @@ class BaseUrlSession(requests.Session):
     def __init__(self, base_url=None):
         if base_url:
             self.base_url = base_url
-        super(BaseUrlSession, self).__init__()
+        super().__init__()
 
     def request(self, method, url, *args, **kwargs):
         """Send the request after generating the complete URL."""
         url = self.create_url(url)
-        return super(BaseUrlSession, self).request(
-            method, url, *args, **kwargs
-        )
+        return super().request(method, url, *args, **kwargs)
 
     def create_url(self, url):
         """Create the URL based off this partial path."""
@@ -383,7 +381,7 @@ class CLIError(Exception):
     standard error message."""
 
     def __init__(self, message, code):
-        super(CLIError, self).__init__(message)
+        super().__init__(message)
         self.code = code
 
 
diff --git a/releasenotes/notes/consolidate-client-subclasses-b6f757c6527edd16.yaml b/releasenotes/notes/consolidate-client-subclasses-b6f757c6527edd16.yaml
new file mode 100644
index 0000000..9239855
--- /dev/null
+++ b/releasenotes/notes/consolidate-client-subclasses-b6f757c6527edd16.yaml
@@ -0,0 +1,22 @@
+---
+upgrade:
+  - |
+    ``dwave.cloud.client`` module is now a namespace containing all client
+    subclasses as submodules. You should update your imports accordingly::
+
+      dwave.cloud.Client         -->  dwave.cloud.client.base.Client
+      dwave.cloud.hybrid.Client  -->  dwave.cloud.client.hybrid.Client
+      dwave.cloud.qpu.Client     -->  dwave.cloud.client.qpu.Client
+      dwave.cloud.sw.Client      -->  dwave.cloud.client.sw.Client
+
+    For backwards compatibility, old import paths still work, but are
+    deprecated.
+deprecations:
+  - |
+    Import paths ``dwave.cloud.{hybrid,qpu,sw}.Client`` are deprecated and
+    should be replaced with ``dwave.cloud.client.{hybrid,qpu,sw}.Client``.
+fixes:
+  - |
+    Configured client session creation is sped up by caching user-agent
+    construction (per client). Client session is now created ~200x faster,
+    making it on par with ``requests.Session`` creation time (~50us).


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index cf2c299..9d18795 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -174,7 +174,7 @@ class ClientConstruction(unittest.TestCase):
 
     def test_default(self):
         conf = {k: k for k in 'endpoint token'.split()}
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.endpoint, 'endpoint')
                 self.assertEqual(client.token, 'token')
@@ -186,7 +186,7 @@ class ClientConstruction(unittest.TestCase):
     def test_client_type(self):
         conf = {k: k for k in 'endpoint token'.split()}
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertIsInstance(client, dwave.cloud.client.Client)
 
@@ -216,7 +216,7 @@ class ClientConstruction(unittest.TestCase):
 
     def test_custom_kwargs(self):
         conf = {k: k for k in 'endpoint token'.split()}
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with mock.patch("dwave.cloud.client.Client.__init__", return_value=None) as init:
                 dwave.cloud.Client.from_config(custom='custom')
                 init.assert_called_once_with(
@@ -224,7 +224,7 @@ class ClientConstruction(unittest.TestCase):
 
     def test_custom_kwargs_overrides_config(self):
         conf = {k: k for k in 'endpoint token custom'.split()}
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with mock.patch("dwave.cloud.client.Client.__init__", return_value=None) as init:
                 dwave.cloud.Client.from_config(custom='new-custom')
                 init.assert_called_once_with(
@@ -235,7 +235,7 @@ class ClientConstruction(unittest.TestCase):
         conf = {k: k for k in 'endpoint token'.split()}
         conf.update(solver=json.dumps(solver_def))
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.default_solver, solver_def)
 
@@ -243,7 +243,7 @@ class ClientConstruction(unittest.TestCase):
         solver_def = {"name__eq": "solver"}
         conf = {k: k for k in 'endpoint token solver'.split()}
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.default_solver, solver_def)
 
@@ -251,7 +251,7 @@ class ClientConstruction(unittest.TestCase):
         new_solver_def = {"software": True}
         conf = {k: k for k in 'endpoint token solver'.split()}
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(solver=new_solver_def) as client:
                 self.assertEqual(client.default_solver, new_solver_def)
 
@@ -262,7 +262,7 @@ class ClientConstruction(unittest.TestCase):
         conf.update(solver=solver_json)
         solver = json.loads(solver_json)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(endpoint=None, solver=None) as client:
                 self.assertEqual(client.endpoint, conf['endpoint'])
                 self.assertEqual(client.default_solver, solver)
@@ -271,14 +271,14 @@ class ClientConstruction(unittest.TestCase):
         conf = {k: k for k in 'endpoint token solver'.split()}
         conf.update(solver=json.dumps({"software": True}))
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(solver='solver') as client:
                 self.assertEqual(client.default_solver, {"name__eq": "solver"})
 
     def test_boolean_options_parsed_from_config(self):
         conf = {'connection_close': 'off', 'permissive_ssl': 'true'}
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(token='token') as client:
                 self.assertFalse(client.connection_close)
                 self.assertTrue(client.permissive_ssl)
@@ -288,7 +288,7 @@ class ClientConstruction(unittest.TestCase):
         DEFAULTS = Client.DEFAULTS.copy()
         DEFAULTS.update(token=token)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: {}):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: {}):
             with mock.patch.multiple("dwave.cloud.Client", DEFAULTS=DEFAULTS):
                 with dwave.cloud.Client.from_config() as client:
                     self.assertEqual(client.token, token)
@@ -313,7 +313,7 @@ class ClientConstruction(unittest.TestCase):
         token = 'value'
         defaults = dict(token=token)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: {}):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: {}):
             with dwave.cloud.Client.from_config(defaults=defaults) as client:
                 self.assertEqual(client.token, token)
 
@@ -333,7 +333,7 @@ class ClientConstruction(unittest.TestCase):
 
         kwargs = dict(token=token, defaults=defaults, request_timeout=None)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with mock.patch.multiple("dwave.cloud.Client", DEFAULTS=DEFAULTS):
                 with dwave.cloud.Client.from_config(**kwargs) as client:
 
@@ -356,7 +356,7 @@ class ClientConstruction(unittest.TestCase):
         """
         conf = dict(token='token', headers=headers_str)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertDictEqual(client.headers, headers_dict)
 
@@ -366,12 +366,12 @@ class ClientConstruction(unittest.TestCase):
         conf = dict(token='token')
 
         # headers as dict
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(headers=headers_dict) as client:
                 self.assertDictEqual(client.headers, headers_dict)
 
         # headers as str
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(headers=headers_str) as client:
                 self.assertDictEqual(client.headers, headers_dict)
 
@@ -383,7 +383,7 @@ class ClientConstruction(unittest.TestCase):
         client_cert = crt
         conf = dict(token='token', client_cert=crt)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.client_cert, client_cert)
 
@@ -394,7 +394,7 @@ class ClientConstruction(unittest.TestCase):
         client_cert = (crt, key)
         conf = dict(token='token', client_cert=crt, client_cert_key=key)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.client_cert, client_cert)
 
@@ -413,7 +413,7 @@ class ClientConstruction(unittest.TestCase):
         client_cert = crt
         conf = dict(client_cert=crt)
 
-        with mock.patch("dwave.cloud.client.load_config", load_config):
+        with mock.patch("dwave.cloud.client.base.load_config", load_config):
             with dwave.cloud.Client.from_config(**conf) as client:
                 self.assertEqual(client.client_cert, client_cert)
 
@@ -421,7 +421,7 @@ class ClientConstruction(unittest.TestCase):
         client_cert = (crt, key)
         conf = dict(client_cert=crt, client_cert_key=key)
 
-        with mock.patch("dwave.cloud.client.load_config", load_config):
+        with mock.patch("dwave.cloud.client.base.load_config", load_config):
             with dwave.cloud.Client.from_config(**conf) as client:
                 self.assertEqual(client.client_cert, client_cert)
 
@@ -429,7 +429,7 @@ class ClientConstruction(unittest.TestCase):
         client_cert = (crt, key)
         conf = dict(client_cert=client_cert)
 
-        with mock.patch("dwave.cloud.client.load_config", load_config):
+        with mock.patch("dwave.cloud.client.base.load_config", load_config):
             with dwave.cloud.Client.from_config(**conf) as client:
                 self.assertEqual(client.client_cert, client_cert)
 
@@ -438,14 +438,14 @@ class ClientConstruction(unittest.TestCase):
         conf = dict(token='token', **poll_conf)
 
         # polling params from config file propagated to client object
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.poll_backoff_min, 0.1)
                 self.assertEqual(client.poll_backoff_max, 1.0)
 
         # test defaults
         conf = dict(token='token')
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 self.assertEqual(client.poll_backoff_min, Client.DEFAULTS['poll_backoff_min'])
                 self.assertEqual(client.poll_backoff_max, Client.DEFAULTS['poll_backoff_max'])
@@ -454,7 +454,7 @@ class ClientConstruction(unittest.TestCase):
         poll_conf = {"poll_backoff_min": "0.1", "poll_backoff_max": "1"}
         conf = dict(token='token', **poll_conf)
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(poll_backoff_min=0.5) as client:
                 self.assertEqual(client.poll_backoff_min, 0.5)
                 self.assertEqual(client.poll_backoff_max, 1.0)
@@ -482,7 +482,7 @@ class ClientConstruction(unittest.TestCase):
         conf = dict(token='token', **retry_conf)
 
         # http retry params from config file propagated to client object
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 for opt, val in retry_opts.items():
                     self.assertEqual(getattr(client, opt), val,
@@ -494,7 +494,7 @@ class ClientConstruction(unittest.TestCase):
 
         # test defaults
         conf = dict(token='token')
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config() as client:
                 for param in retry_conf:
                     self.assertEqual(getattr(client, param), Client.DEFAULTS[param])
@@ -511,7 +511,7 @@ class ClientConstruction(unittest.TestCase):
         }
         conf = dict(token='token')
 
-        with mock.patch("dwave.cloud.client.load_config", lambda **kw: conf):
+        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: conf):
             with dwave.cloud.Client.from_config(**retry_kwargs) as client:
                 # verify client final config
                 for opt, val in retry_kwargs.items():
diff --git a/tests/test_mock_solver_loading.py b/tests/test_mock_solver_loading.py
index edc706f..6e458ec 100644
--- a/tests/test_mock_solver_loading.py
+++ b/tests/test_mock_solver_loading.py
@@ -21,10 +21,11 @@ from unittest import mock
 
 import requests_mock
 
-from dwave.cloud.client import Client, Solver
+from dwave.cloud.client import Client
 from dwave.cloud.qpu import Client as QPUClient
 from dwave.cloud.sw import Client as SoftwareClient
 from dwave.cloud.hybrid import Client as HybridClient
+from dwave.cloud.solver import Solver
 from dwave.cloud.exceptions import *
 from dwave.cloud.config import load_config
 
diff --git a/tests/test_mock_submission.py b/tests/test_mock_submission.py
index ad8432b..f2e80d6 100644
--- a/tests/test_mock_submission.py
+++ b/tests/test_mock_submission.py
@@ -37,7 +37,8 @@ except ImportError:
     dimod = None
 
 from dwave.cloud.utils import evaluate_ising, generate_const_ising_problem
-from dwave.cloud.client import Client, Solver
+from dwave.cloud.client import Client
+from dwave.cloud.solver import Solver
 from dwave.cloud.computation import Future
 from dwave.cloud.exceptions import (
     SolverFailureError, CanceledFutureError, SolverError,