Repository: holoviz/holoviews

Issue:
Confusing error from hv.opts prior to hv.extension()
Current published and master versions of HoloViews give a bewildering error when trying to create element-specific options objects:

<img width="1114" alt="image" src="https://user-images.githubusercontent.com/1695496/193338996-53fb42ad-d0b8-47be-82d0-ca5d67947f46.png">

hv.opts apparently only populates its various accessors when hv.extension loads, but the error message doesn't convey anything to me about "run hv.extension()". I've encountered this issue several times over the years, usually when I'm trying to isolate some code that (in my mind) has nothing to do with a particular backend. Each time I get completely sidetracked -- I'm trying to debug one thing, and now suddenly I can't even set an option on hv.opts.Image? It's particularly a problem in a Jupyter notebook, when I restart the kernel, then try to look at a tiny bit of code that has nothing to do with actual plotting, and suddenly things like this don't work. Eventually I do figure out that it's because hv.extension needs to be run, but each time I've now wasted a good bit of time.

Is there a way that instead of failing with this obscure message, instead it could print something helpful, like "No subobject of type Image has been registered; maybe you need to run hv.extension to declare a plotting backend?"?


Discussion:
A way to get a better message is with a metaclass:

``` python
import param
from param.parameterized import ParameterizedMetaclass


class OptsMeta(ParameterizedMetaclass):
    def __getattr__(self, attr):
        try:
            return super().__getattr__(attr)
        except AttributeError:
            msg = f"No subobject of type {attr} has been registered; maybe you need to run 'hv.extension' to declare a plotting backend?"
            raise AttributeError(msg) from None


class opts(param.ParameterizedFunction, metaclass=OptsMeta):
    pass
```

![image](https://user-images.githubusercontent.com/19758978/195814249-ae5b85a8-b4ad-4d32-b3da-aa2a491bf7a3.png)


Sounds good to me! @jlstevens ?
Yes, I like this metaclass approach!

The message is good but wouldn't it appear for other missing attributes, e.g. due to typos (`opts.Curvve`).

I think a better message might be:

```
No entry for 'Curve' registered; this name may not refer to a valid object 
or you may need to run `hv.extension` to select a plotting backend.
```

Would be happy to merge a PR using this approach/message.
Good point; sounds good.

Resolving patch:
diff --git a/holoviews/util/__init__.py b/holoviews/util/__init__.py
--- a/holoviews/util/__init__.py
+++ b/holoviews/util/__init__.py
@@ -48,8 +48,24 @@ def examples(path='holoviews-examples', verbose=False, force=False, root=__file_
         print('Cannot find %s' % tree_root)
 
 
+class OptsMeta(param.parameterized.ParameterizedMetaclass):
+    """
+    Improve error message when running something
+    like: 'hv.opts.Curve()' without a plotting backend.
+    """
+
+    def __getattr__(self, attr):
+        try:
+            return super().__getattr__(attr)
+        except AttributeError:
+            msg = (
+                f"No entry for {attr!r} registered; this name may not refer to a valid object "
+                "or you may need to run 'hv.extension' to select a plotting backend."
+            )
+            raise AttributeError(msg) from None
+
 
-class opts(param.ParameterizedFunction):
+class opts(param.ParameterizedFunction, metaclass=OptsMeta):
     """
     Utility function to set options at the global level or to provide an
     Options object that can be used with the .options method of an


Test patch: