Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve error when an object fails to serialize #1809

Merged
merged 4 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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

Check warning on line 420 in asdf/yamlutil.py

View check run for this annotation

Codecov / codecov/patch

asdf/yamlutil.py#L420

Added line #L420 was not covered by tests
# 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
Loading