Repository: dwavesystems/dwave-cloud-client
Pull request #629 (2024-06-14 14:29:32)

Remove deprecated stuff for 0.12
Remove:
- `Client()` positional args,
- `Future.result()` aliases: `samples` and `occurrences`.

Reduced scope for 0.12.0.


Patch:
diff --git a/dwave/cloud/client/base.py b/dwave/cloud/client/base.py
index 067d52d..abf29c8 100644
--- a/dwave/cloud/client/base.py
+++ b/dwave/cloud/client/base.py
@@ -248,14 +248,14 @@ class Client(object):
 
         Instance-level defaults can be specified via ``defaults`` argument.
 
-    .. deprecated:: 0.10.0
-        Positional arguments in :class:`.Client` constructor are deprecated and
-        will be removed in 0.12.0.
-
     .. deprecated:: 0.11.0
         Config attributes on :class:`.Client` are deprecated in favor of config
         model attributes available on ``Client.config`` and will be removed
-        in 0.12.0.
+        in 0.13.0.
+
+    .. versionremoved:: 0.12.0
+        Positional arguments in :class:`.Client` constructor, deprecated in 0.10.0,
+        are removed in 0.12.0. Use keyword arguments instead.
 
     Examples:
         This example directly initializes a :class:`.Client`.
@@ -359,7 +359,7 @@ class Client(object):
     def _legacy_config_property_proxy(self, legacy_property, config_path):
         warnings.warn(
             f"`Client.{legacy_property}` property is deprecated since "
-            f"dwave-cloud-client 0.11.0, and will be removed in 0.12.0. "
+            f"dwave-cloud-client 0.11.0, and will be removed in 0.13.0. "
             f"Use `Client.config.{config_path}` instead.",
             DeprecationWarning, stacklevel=2)
         return pluck(self.config, config_path)
@@ -435,27 +435,7 @@ class Client(object):
         return _clients[_client](**config)
 
     @dispatches_events('client_init')
-    def __init__(self, *args, **kwargs):
-        # for (reasonable) backwards compatibility, accept only the first few
-        # positional args.
-        if len(args) > 3:
-            raise TypeError(
-                "Client constructor takes up to 3 positional "
-                f"arguments, but {len(args)} were given")
-
-        if len(args) > 0:
-            warnings.warn(
-                "Positional arguments in Client constructor are deprecated "
-                "since dwave-cloud-client 0.10.0, and will be removed in 0.12.0. "
-                "Use keyword arguments instead.",
-                DeprecationWarning, stacklevel=3)
-
-            argsdict = dict(zip(('endpoint', 'token', 'solver'), args))
-            intersection = argsdict.keys() & kwargs
-            if intersection:
-                raise TypeError(f"Client() got multiple values for {intersection}")
-            kwargs.update(argsdict)
-
+    def __init__(self, **kwargs):
         logger.debug("Client init called with: %r", kwargs)
 
         # insert deprecated config properties that proxy values from the new `Client.config`
diff --git a/dwave/cloud/computation.py b/dwave/cloud/computation.py
index 1b008d3..3ea9b22 100644
--- a/dwave/cloud/computation.py
+++ b/dwave/cloud/computation.py
@@ -566,14 +566,6 @@ class Future(object):
             `'num_occurrences'`. Better yet, use :meth:`Future.samples` and
             :meth:`Future.num_occurrences` instead.
 
-        .. deprecated:: 0.8.0
-
-            Alias keys ``samples`` and ``occurrences`` in the result dict are
-            deprecated and will be removed in 0.12.0. We'll try to keep the
-            result dict as close to raw data returned by SAPI as possible.
-            Postprocessed data is available via
-            :class:`~dwave.cloud.computation.Future` properties.
-
         .. versionchanged:: 0.8.0
 
             Instead of adding copies of ``solutions`` and ``num_occurrences``
@@ -581,6 +573,14 @@ class Future(object):
             :class:`~dwave.cloud.utils.decorators.aliasdict`. Values are available under
             alias keys, but the keys themselves are not stored or visible.
 
+        .. versionremoved:: 0.12.0
+
+            Alias keys ``samples`` and ``occurrences`` in the result dict,
+            deprecated in 0.8.0, are removed in 0.12.0. We'll try to keep the
+            result dict as close to raw data returned by SAPI as possible.
+            Postprocessed data is available via
+            :class:`~dwave.cloud.computation.Future` properties.
+
         Examples:
             This example creates a solver using the local system's default
             D-Wave Cloud Client configuration file, submits a simple QUBO
@@ -916,7 +916,6 @@ class Future(object):
                 # extract results from the response
                 # note: decoding might mutate `self._message`, so it should be only called once
                 self._decode()
-                self._alias_result()
 
             # signal answer data downloaded
             if 'answer' in self._result:
@@ -961,26 +960,3 @@ class Future(object):
         self._result = self.solver.decode_response(self._message, answer_data=self._answer_data)
         self.parse_time = time.time() - start
         return self._result
-
-    def _alias_result(self):
-        """Alias `solutions` and `num_occurrences`.
-
-        Deprecated in version 0.8.0.
-
-        Scheduled for removal in 0.12.0.
-        """
-        if not self._result:
-            return
-
-        msg = "'{}' alias has been deprecated in favor of '{}'"
-        samples_msg = msg.format('samples', 'solutions')
-        occurrences_msg = msg.format('occurrences', 'num_occurrences')
-
-        aliases = dict(
-            samples=deprecated(samples_msg)(itemgetter('solutions')),
-            occurrences=deprecated(occurrences_msg)(itemgetter('num_occurrences')))
-
-        self._result = aliasdict(self._result)
-        self._result.alias(aliases)
-
-        return self._result
diff --git a/releasenotes/notes/remove-deprecated-functionality-in-0.12-6053e8711b80b22b.yaml b/releasenotes/notes/remove-deprecated-functionality-in-0.12-6053e8711b80b22b.yaml
new file mode 100644
index 0000000..19d3ade
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-functionality-in-0.12-6053e8711b80b22b.yaml
@@ -0,0 +1,11 @@
+---
+upgrade:
+  - |
+    Positional arguments in ``dwave.cloud.Client`` constructor, deprecated in 0.10.0,
+    are removed in 0.12.0. Use keyword arguments instead.
+  - |
+    Alias keys ``samples`` and ``occurrences`` (for ``solutions`` and ``num_occurrences``)
+    in the results dict of ``dwave.cloud.computation.Future.result()``, deprecated in
+    0.8.0, are now finally removed in 0.12.0. We'll try to keep the result dict as close
+    to raw data returned by SAPI as possible. Postprocessed data is instead available
+    via ``dwave.cloud.computation.Future`` properties.


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 1c4c791..c835999 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -620,34 +620,6 @@ class ClientConstruction(unittest.TestCase):
                 retry = client.session.get_adapter('https://').max_retries
                 self._verify_retry_config(retry, retry_kwargs)
 
-    def test_positional_args(self):
-        with mock.patch("dwave.cloud.client.base.load_config", lambda **kw: {}):
-
-            # up to 3 positional args are allowed
-            endpoint, token, solver = 'endpoint token solver'.split()
-            with dwave.cloud.Client(endpoint, token, solver) as client:
-                self.assertEqual(client.config.endpoint, endpoint)
-                self.assertEqual(client.config.token, token)
-                self.assertEqual(client.config.solver, {'name__eq': solver})
-
-            # but they are deprecated
-            with self.assertWarns(DeprecationWarning):
-                with dwave.cloud.Client(endpoint, token):
-                    pass
-
-            # and more than 3 are not allowed
-            with self.assertRaises(TypeError):
-                dwave.cloud.Client(endpoint, token, solver, 'another')
-
-            # with clashes between pargs and kwargs not allowed as well
-            with self.assertRaises(TypeError):
-                dwave.cloud.Client(endpoint, endpoint=endpoint)
-
-            # even though non-conflicting combination is allowed
-            with dwave.cloud.Client(endpoint, token=token) as client:
-                self.assertEqual(client.config.endpoint, endpoint)
-                self.assertEqual(client.config.token, token)
-
 
 @mock.patch("dwave.cloud.regions.get_regions", get_default_regions)
 class ClientConfigIntegration(unittest.TestCase):
diff --git a/tests/test_mock_submission.py b/tests/test_mock_submission.py
index e7c15ad..bba0faf 100644
--- a/tests/test_mock_submission.py
+++ b/tests/test_mock_submission.py
@@ -906,39 +906,6 @@ class TestOffsetHandling(_QueryTest):
                     self._check(results, linear, quadratic, offset=offset, **params)
 
 
-@mock.patch('time.sleep', lambda *x: None)
-class TestComputationDeprecations(_QueryTest):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.sapi = StructuredSapiMockResponses()
-
-    def test_deprecations(self):
-        """Proper deprecation warnings are raised."""
-
-        def create_mock_session(client):
-            session = mock.Mock()
-            session.post = lambda a, _: choose_reply(a, {
-                'problems/': [self.sapi.complete_no_answer_reply(id='123')]})
-            session.get = lambda a: choose_reply(a, {
-                'problems/123/': self.sapi.complete_reply(id='123')})
-            return session
-
-        with mock.patch.object(Client, 'create_session', create_mock_session):
-            with Client(endpoint='endpoint', token='token') as client:
-                solver = Solver(client, self.sapi.solver.data)
-
-                linear, quadratic = self.sapi.problem
-                params = dict(num_reads=100)
-                results = solver.sample_ising(linear, quadratic, **params)
-
-                # aliased keys are deprecated in 0.8.0
-                with self.assertWarns(DeprecationWarning):
-                    results['samples']
-                with self.assertWarns(DeprecationWarning):
-                    results['occurrences']
-
-
 class TestProblemLabel(unittest.TestCase):
 
     @classmethod
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0263e26..093a030 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -12,21 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import io
+import contextlib
 import copy
+import io
 import json
-import uuid
 import logging
+import tempfile
+import time
 import unittest
+import uuid
 import warnings
-import tempfile
-import contextlib
-from unittest import mock
 from collections import OrderedDict
-from itertools import count
 from datetime import datetime
+from dateutil.tz import UTC
 from functools import partial, wraps
+from itertools import count
 from typing import Tuple, Union
+from unittest import mock
 
 import numpy
 from parameterized import parameterized
@@ -139,10 +141,10 @@ class TestSimpleUtils(unittest.TestCase):
 
     def test_utcnow(self):
         t = utcnow()
-        now = datetime.utcnow()
+        now = datetime.fromtimestamp(time.time(), tz=UTC)
         self.assertEqual(t.utcoffset().total_seconds(), 0.0)
-        unaware = t.replace(tzinfo=None)
-        self.assertLess((now - unaware).total_seconds(), 1.0)
+        self.assertEqual(now.utcoffset().total_seconds(), 0.0)
+        self.assertLess((now - t).total_seconds(), 1.0)
 
     def test_parse_loglevel_invalid(self):
         """Parsing invalid log levels returns NOTSET."""