Repository: dwavesystems/dimod
Issue: Ocean build from source fails

Introducing `dwave-preprocessing` as a full dependency in #924 is causing build from source of dwave-preprocessing (and consequently the whole Ocean) to fail. Note: dwave-preprocessing has dimod as a build dependency.

See https://github.com/dwavesystems/dwave-preprocessing/issues/26.

Proposed solution: revert to optional dependency on dwave-preprocessing until we remove it completely in 0.11.

Changed files:
dimod/reference/composites/_preprocessing.py, dimod/reference/composites/spin_transform.py, dimod/reference/composites/fixedvariable.py, dimod/reference/composites/clipcomposite.py, dimod/reference/composites/scalecomposite.py, dimod/reference/composites/roofduality.py, dimod/reference/composites/connectedcomponent.py

diff --git a/dimod/reference/composites/_preprocessing.py b/dimod/reference/composites/_preprocessing.py
index 74acc46f..6ffb53a9 100644
--- a/dimod/reference/composites/_preprocessing.py
+++ b/dimod/reference/composites/_preprocessing.py
@@ -16,11 +16,25 @@
 
 import warnings
 
-__all__ = ['DeprecatedToPreprocessing']
+__all__ = ['NotFound', 'DeprecatedToPreprocessing']
+
+
+class NotFound:
+    pass
 
 
 class DeprecatedToPreprocessing:
     def __init__(self, *args, **kwargs):
+        if isinstance(self, NotFound):
+            # we recommend --no-deps because its dependencies are the same as
+            # dimods and it would be a circular install otherwise
+            raise TypeError(
+                f"{type(self).__name__!r} has been moved to dwave-preprocessing. "
+                "You must install dwave-preprocessing in order to use it. "
+                "You can do so with "
+                "'pip install \\"dwave-preprocessing<0.4\\" --no-deps'.",
+                )
+
         # otherwise warn about it's new location but let it proceed
         warnings.warn(
             f"{type(self).__name__!s} has been moved to dwave-preprocessing "
diff --git a/dimod/reference/composites/clipcomposite.py b/dimod/reference/composites/clipcomposite.py
index 1b42e234..0127ef82 100644
--- a/dimod/reference/composites/clipcomposite.py
+++ b/dimod/reference/composites/clipcomposite.py
@@ -18,7 +18,10 @@ and upper bounds is not given it does nothing.
 
 """
 
-from dwave.preprocessing import ClipComposite as _ClipComposite
+try:
+    from dwave.preprocessing import ClipComposite as _ClipComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as _ClipComposite
 
 from dimod.reference.composites._preprocessing import DeprecatedToPreprocessing
 
diff --git a/dimod/reference/composites/connectedcomponent.py b/dimod/reference/composites/connectedcomponent.py
index 8bf452ad..b3d069ee 100644
--- a/dimod/reference/composites/connectedcomponent.py
+++ b/dimod/reference/composites/connectedcomponent.py
@@ -18,7 +18,10 @@ connected components of the binary
 quadratic model graph before sending to its child sampler.
 """
 
-from dwave.preprocessing import ConnectedComponentsComposite as _ConnectedComponentsComposite
+try:
+    from dwave.preprocessing import ConnectedComponentsComposite as _ConnectedComponentsComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as _ConnectedComponentsComposite
 
 from dimod.reference.composites._preprocessing import DeprecatedToPreprocessing
 
diff --git a/dimod/reference/composites/fixedvariable.py b/dimod/reference/composites/fixedvariable.py
index 5e38ec33..98dbfb71 100644
--- a/dimod/reference/composites/fixedvariable.py
+++ b/dimod/reference/composites/fixedvariable.py
@@ -18,7 +18,12 @@ quadratic model before sending to its child sampler.
 """
 import warnings
 
-from dwave.preprocessing import FixVariablesComposite
+try:
+    from dwave.preprocessing import FixVariablesComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as FixVariablesComposite
+
+from dimod.reference.composites._preprocessing import NotFound
 
 __all__ = ['FixedVariableComposite']
 
@@ -44,6 +49,16 @@ class FixedVariableComposite(FixVariablesComposite):
 
     """
     def __init__(self, child):
+        if isinstance(self, NotFound):
+            # we recommend --no-deps because its dependencies are the same as
+            # dimods and it would be a circular install otherwise
+            raise TypeError(
+                f"{type(self).__name__!r} has been moved to dwave-preprocessing. "
+                "You must install dwave-preprocessing in order to use it. "
+                "You can do so with "
+                "'pip install \\"dwave-preprocessing<0.4\\" --no-deps'.",
+                )
+
         # otherwise warn about it's new location but let it proceed
         warnings.warn(
             f"{type(self).__name__!s} has been deprecated and will be removed from dimod 0.11.0. "
diff --git a/dimod/reference/composites/roofduality.py b/dimod/reference/composites/roofduality.py
index 9cd6bb49..5b2b1701 100644
--- a/dimod/reference/composites/roofduality.py
+++ b/dimod/reference/composites/roofduality.py
@@ -25,7 +25,12 @@ sampler.
 """
 import warnings
 
-from dwave.preprocessing import FixVariablesComposite
+try:
+    from dwave.preprocessing import FixVariablesComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as FixVariablesComposite
+
+from dimod.reference.composites._preprocessing import NotFound
 
 __all__ = ['RoofDualityComposite']
 
@@ -44,6 +49,16 @@ class RoofDualityComposite(FixVariablesComposite):
 
     """
     def __init__(self, child):
+        if isinstance(self, NotFound):
+            # we recommend --no-deps because its dependencies are the same as
+            # dimods and it would be a circular install otherwise
+            raise TypeError(
+                f"{type(self).__name__!r} has been moved to dwave-preprocessing. "
+                "You must install dwave-preprocessing in order to use it. "
+                "You can do so with "
+                "'pip install \\"dwave-preprocessing<0.4\\" --no-deps'.",
+                )
+
         # otherwise warn about it's new location but let it proceed
         warnings.warn(
             f"{type(self).__name__!s} has been deprecated and will be removed from dimod 0.11.0. "
diff --git a/dimod/reference/composites/scalecomposite.py b/dimod/reference/composites/scalecomposite.py
index c41e9936..faf0804b 100644
--- a/dimod/reference/composites/scalecomposite.py
+++ b/dimod/reference/composites/scalecomposite.py
@@ -18,7 +18,10 @@ not specified, calculates it based on quadratic and bias ranges.
 
 """
 
-from dwave.preprocessing import ScaleComposite as _ScaleComposite
+try:
+    from dwave.preprocessing import ScaleComposite as _ScaleComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as _ScaleComposite
 
 from dimod.reference.composites._preprocessing import DeprecatedToPreprocessing
 
diff --git a/dimod/reference/composites/spin_transform.py b/dimod/reference/composites/spin_transform.py
index 287d5613..04136d26 100644
--- a/dimod/reference/composites/spin_transform.py
+++ b/dimod/reference/composites/spin_transform.py
@@ -22,7 +22,10 @@ the transform simply amounts to reinterpreting spin up as spin down, and visa-ve
 a particular spin.
 """
 
-from dwave.preprocessing import SpinReversalTransformComposite as _SpinReversalTransformComposite
+try:
+    from dwave.preprocessing import SpinReversalTransformComposite as _SpinReversalTransformComposite
+except ImportError:
+    from dimod.reference.composites._preprocessing import NotFound as _SpinReversalTransformComposite
 
 from dimod.reference.composites._preprocessing import DeprecatedToPreprocessing