Skip to content

Commit

Permalink
Merge pull request #1809 from braingram/serialization_error
Browse files Browse the repository at this point in the history
improve error when an object fails to serialize
  • Loading branch information
braingram authored Jul 23, 2024
2 parents b8ba0ad + af4a8c5 commit 15a9f2a
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

- Fix issue where roundtripping a masked array with no masked values removes the mask [#1803]

- Use a custom exception ``AsdfSerializationError`` to indicate when an object in the
tree fails to be serialized by asdf (and by yaml). This exception currently inherits
from ``yaml.representer.RepresenterError`` to provide backwards compatibility. However
this inheritance may be dropped in a future asdf version. Please migrate to the new
``AsdfSerializationError``. [#1809]

3.3.0 (2024-07-12)
------------------

Expand Down
5 changes: 2 additions & 3 deletions asdf/_tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

import pytest
from packaging.specifiers import SpecifierSet
from yaml.representer import RepresenterError

import asdf
from asdf import AsdfFile, config_context
from asdf.exceptions import AsdfManifestURIMismatchWarning, AsdfWarning, ValidationError
from asdf.exceptions import AsdfManifestURIMismatchWarning, AsdfSerializationError, AsdfWarning, ValidationError
from asdf.extension import (
Compressor,
Converter,
Expand Down Expand Up @@ -578,7 +577,7 @@ class FooExtension(Extension):
tree = {"obj": Foo()}
with config_context() as cfg:
cfg.add_extension(FooExtension())
with pytest.raises(RepresenterError, match=r"cannot represent an object"):
with pytest.raises(AsdfSerializationError, match=r"is not serializable by asdf"):
roundtrip_object(tree)


Expand Down
6 changes: 3 additions & 3 deletions asdf/_tests/test_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import asdf
from asdf import tagged, treeutil, yamlutil
from asdf.exceptions import AsdfConversionWarning, AsdfDeprecationWarning, AsdfWarning
from asdf.exceptions import AsdfConversionWarning, AsdfDeprecationWarning, AsdfSerializationError, AsdfWarning
from asdf.testing.helpers import yaml_to_asdf


Expand Down Expand Up @@ -104,7 +104,7 @@ class Foo:

buff = io.BytesIO()
ff = asdf.AsdfFile(tree)
with pytest.raises(yaml.YAMLError, match=r"\('cannot represent an object', .*\)"):
with pytest.raises(AsdfSerializationError, match=r".*is not serializable by asdf.*"):
ff.write_to(buff)


Expand Down Expand Up @@ -350,7 +350,7 @@ class MyNDArray(np.ndarray):

with asdf.config.config_context() as cfg:
cfg.convert_unknown_ndarray_subclasses = False
with pytest.raises(yaml.representer.RepresenterError, match=r".*cannot represent.*"):
with pytest.raises(AsdfSerializationError, match=r".*is not serializable by asdf.*"):
af.write_to(fn)


Expand Down
11 changes: 11 additions & 0 deletions asdf/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from yaml.representer import RepresenterError

from asdf._jsonschema import ValidationError

__all__ = [
Expand All @@ -7,6 +9,7 @@
"AsdfManifestURIMismatchWarning",
"AsdfPackageVersionWarning",
"AsdfProvisionalAPIWarning",
"AsdfSerializationError",
"AsdfWarning",
"DelimiterNotFoundError",
"ValidationError",
Expand Down Expand Up @@ -73,3 +76,11 @@ class AsdfLazyReferenceError(ReferenceError):
collected and you may need to update your code to keep the AsdfFile
in memory (by keeping a reference).
"""


class AsdfSerializationError(RepresenterError):
"""
An object failed serialization by asdf and by yaml. This likely indicates
that the object does not have a supporting asdf Converter and needs to
be manually converted to a supported type.
"""
36 changes: 24 additions & 12 deletions asdf/yamlutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from . import config, schema, tagged, treeutil, util
from .constants import STSCI_SCHEMA_TAG_BASE, YAML_TAG_PREFIX
from .exceptions import AsdfConversionWarning
from .exceptions import AsdfConversionWarning, AsdfSerializationError
from .extension._serialization_context import BlockAccess
from .tags.core import AsdfObject
from .versioning import _YAML_VERSION, _yaml_base_loader
Expand Down Expand Up @@ -403,14 +403,26 @@ def dump_tree(tree, fd, ctx, tree_finalizer=None, _serialization_context=None):
if key not in tags:
tags[key] = val

yaml.dump_all(
[tree],
stream=fd,
Dumper=AsdfDumper,
explicit_start=True,
explicit_end=True,
version=_YAML_VERSION,
allow_unicode=True,
encoding="utf-8",
tags=tags,
)
try:
yaml.dump_all(
[tree],
stream=fd,
Dumper=AsdfDumper,
explicit_start=True,
explicit_end=True,
version=_YAML_VERSION,
allow_unicode=True,
encoding="utf-8",
tags=tags,
)
except yaml.representer.RepresenterError as err:
if len(err.args) < 2:
raise err
# inspect the exception arguments to determine what object failed
obj = err.args[1]
msg = (
f"Object of type[{type(obj)}] is not serializable by asdf. "
"Please convert the object to a supported type or implement "
"a Converter for this type to allow the tree to be serialized."
)
raise AsdfSerializationError(msg, obj) from err
6 changes: 6 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@

nitpicky = True

# ignore a few pyyaml docs links since they don't appear to support intersphinx
nitpick_ignore = [
("py:class", "yaml.representer.RepresenterError"),
("py:class", "yaml.error.YAMLError"),
]

# Add intersphinx mappings
intersphinx_mapping["semantic_version"] = ("https://python-semanticversion.readthedocs.io/en/latest/", None)
intersphinx_mapping["jsonschema"] = ("https://python-jsonschema.readthedocs.io/en/stable/", None)
Expand Down

0 comments on commit 15a9f2a

Please sign in to comment.