Repository: asdf-format/asdf

Issue:
asdftool repr for Time objects
Using `asdftool info <file>` on an ASDF file containing astropy.time.Time objects produces the following (excerpted) output:
```
  │ │ ├─type (str): WFI_IMAGE
  │ │ ├─start_time (Time) # UTC exposure start time
  │ │ ├─mid_time (Time) # UTC exposure mid time
  │ │ ├─end_time (Time) # UTC exposure end time
```
Is it possible to add some kind of repr to this to display the time (maybe in ISOT or some other format?).

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 62a76753..e90e7540 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -28,6 +28,9 @@ The ASDF Standard is at v1.6.0
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 - Fix bug in ``asdftool diff`` for arrays within a list [#1672]
+- For ``info`` and ``search`` show ``str`` representation of childless
+  (leaf) nodes if ``show_values`` is enabled  [#1687]
+- Deprecate ``asdf.util.is_primitive`` [#1687]
 
 3.0.0 (2023-10-16)
 ------------------
diff --git a/asdf/_display.py b/asdf/_display.py
index 8f3bfad7..d7c08934 100644
--- a/asdf/_display.py
+++ b/asdf/_display.py
@@ -11,7 +11,6 @@ import numpy as np
 
 from ._node_info import create_tree
 from .tags.core.ndarray import NDArrayType
-from .util import is_primitive
 
 __all__ = [
     "DEFAULT_MAX_ROWS",
@@ -252,12 +251,13 @@ class _TreeRenderer:
 
     def _render_node_value(self, info):
         rendered_type = type(info.node).__name__
-        if is_primitive(info.node) and self._show_values:
-            return f"({rendered_type}): {info.node}"
 
         if isinstance(info.node, (NDArrayType, np.ndarray)):
             return f"({rendered_type}): shape={info.node.shape}, dtype={info.node.dtype.name}"
 
+        if not info.children and self._show_values:
+            return f"({rendered_type}): {info.node}"
+
         return f"({rendered_type})"
 
     def _make_prefix(self, depth, active_depths, is_tail):
diff --git a/asdf/util.py b/asdf/util.py
index 36cafd3e..875e57f8 100644
--- a/asdf/util.py
+++ b/asdf/util.py
@@ -20,8 +20,7 @@ import numpy as np
 from importlib_metadata import packages_distributions
 from packaging.version import Version
 
-from . import constants
-from .exceptions import AsdfDeprecationWarning
+from . import constants, exceptions
 
 # We're importing our own copy of urllib.parse because
 # we need to patch it to support asdf:// URIs, but it'd
@@ -78,7 +77,7 @@ def human_list(line, separator="and"):
     >>> human_list(["vanilla", "strawberry", "chocolate"], "or")  # doctest: +SKIP
     'vanilla, strawberry or chocolate'
     """
-    warnings.warn("asdf.util.human_list is deprecated", AsdfDeprecationWarning)
+    warnings.warn("asdf.util.human_list is deprecated", exceptions.AsdfDeprecationWarning)
     if len(line) == 1:
         return line[0]
 
@@ -126,7 +125,7 @@ def iter_subclasses(cls):
     """
     Returns all subclasses of a class.
     """
-    warnings.warn("asdf.util.iter_subclasses is deprecated", AsdfDeprecationWarning)
+    warnings.warn("asdf.util.iter_subclasses is deprecated", exceptions.AsdfDeprecationWarning)
     yield from _iter_subclasses(cls)
 
 
@@ -289,7 +288,9 @@ def resolve_name(name):
         If the module or named object is not found.
     """
 
-    warnings.warn("asdf.util.resolve_name is deprecated, see astropy.utils.resolve_name", AsdfDeprecationWarning)
+    warnings.warn(
+        "asdf.util.resolve_name is deprecated, see astropy.utils.resolve_name", exceptions.AsdfDeprecationWarning
+    )
 
     # Note: On python 2 these must be str objects and not unicode
     parts = [str(part) for part in name.split(".")]
@@ -366,7 +367,7 @@ def minversion(module, version, inclusive=True):
         as opposed to strictly greater than (default: `True`).
     """
 
-    warnings.warn("asdf.util.minversion is deprecated, see astropy.utils.minversion", AsdfDeprecationWarning)
+    warnings.warn("asdf.util.minversion is deprecated, see astropy.utils.minversion", exceptions.AsdfDeprecationWarning)
 
     if isinstance(module, types.ModuleType):
         module_name = module.__name__
@@ -376,7 +377,7 @@ def minversion(module, version, inclusive=True):
         module_version = None
         try:
             with warnings.catch_warnings():
-                warnings.filterwarnings("ignore", "asdf.util.resolve_name", AsdfDeprecationWarning)
+                warnings.filterwarnings("ignore", "asdf.util.resolve_name", exceptions.AsdfDeprecationWarning)
                 module = resolve_name(module_name)
         except ImportError:
             return False
@@ -468,6 +469,7 @@ def is_primitive(value):
     bool
         True if the value is primitive, False otherwise
     """
+    warnings.warn("asdf.util.is_primitive is deprecated", exceptions.AsdfDeprecationWarning)
     return isinstance(value, (bool, int, float, complex, str)) or value is None
 
 


Test patch:
diff --git a/asdf/_tests/test_deprecated.py b/asdf/_tests/test_deprecated.py
index 0df6b1f7..5a749d29 100644
--- a/asdf/_tests/test_deprecated.py
+++ b/asdf/_tests/test_deprecated.py
@@ -67,3 +67,8 @@ def test_find_references_during_open_deprecation(tmp_path):
     with pytest.warns(AsdfDeprecationWarning, match="find_references during open"):
         with asdf.open(fn) as af:
             pass
+
+
+def test_asdf_util_is_primitive_deprecation():
+    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.is_primitive is deprecated"):
+        asdf.util.is_primitive(1)
diff --git a/asdf/_tests/test_info.py b/asdf/_tests/test_info.py
index 932db910..79a89273 100644
--- a/asdf/_tests/test_info.py
+++ b/asdf/_tests/test_info.py
@@ -633,6 +633,9 @@ class RecursiveObjectWithInfoSupport:
     def __asdf_traverse__(self):
         return {"the_meaning": self.the_meaning, "clown": self.clown, "recursive": self.recursive}
 
+    def __str__(self):
+        return "rec ref"
+
 
 def test_recursive_info_object_support(capsys, tmp_path):
     tempdir = pathlib.Path(tempfile.mkdtemp())
diff --git a/asdf/_tests/test_util.py b/asdf/_tests/test_util.py
index a0830b61..e7c6345d 100644
--- a/asdf/_tests/test_util.py
+++ b/asdf/_tests/test_util.py
@@ -8,11 +8,13 @@ from asdf.exceptions import AsdfDeprecationWarning
 
 
 def test_is_primitive():
-    for value in [None, "foo", 1, 1.39, 1 + 1j, True]:
-        assert util.is_primitive(value) is True
+    with warnings.catch_warnings():
+        warnings.filterwarnings("ignore", "asdf.util.is_primitive is deprecated", AsdfDeprecationWarning)
+        for value in [None, "foo", 1, 1.39, 1 + 1j, True]:
+            assert util.is_primitive(value) is True
 
-    for value in [[], (), {}, set()]:
-        assert util.is_primitive(value) is False
+        for value in [[], (), {}, set()]:
+            assert util.is_primitive(value) is False
 
 
 def test_not_set():