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

Configure warnings #319

Merged
merged 9 commits into from
Sep 12, 2017
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
- When possible, display name of ASDF file that caused version mismatch
warning. [#306]

- Issue a warning when an unrecognized tag is encountered. [#295]
- Issue a warning when an unrecognized tag is encountered. [#295] This warning
is silenced by default, but can be enabled with a parameter to the
``AsdfFile`` constructor, or to ``AsdfFile.open``. Also added an option for
ignoring warnings from unrecognized schema tags. [#319]

- Fix bug with loading JSON schemas in Python 3.5. [#317]

Expand Down
34 changes: 26 additions & 8 deletions asdf/asdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class AsdfFile(versioning.VersionedMixin):
"""
The main class that represents a ASDF file.
"""
def __init__(self, tree=None, uri=None, extensions=None, version=None):
def __init__(self, tree=None, uri=None, extensions=None, version=None,
ignore_version_mismatch=True, ignore_unrecognized_tag=False):
"""
Parameters
----------
Expand All @@ -65,7 +66,16 @@ def __init__(self, tree=None, uri=None, extensions=None, version=None):
The ASDF version to use when writing out. If not
provided, it will write out in the latest version
supported by asdf.

ignore_version_mismatch : bool, optional
When `True`, do not raise warnings for mismatched schema versions.
Set to `True` by default.

ignore_unrecognized_tag : bool, optional
When `True`, do not raise warnings for unrecognized tags. Set to
`False` by default.
"""

if extensions is None or extensions == []:
self._extensions = extension._builtin_extension_list
else:
Expand All @@ -77,6 +87,9 @@ def __init__(self, tree=None, uri=None, extensions=None, version=None):
extensions.insert(0, extension.BuiltinExtension())
self._extensions = extension.AsdfExtensionList(extensions)

self._ignore_version_mismatch = ignore_version_mismatch
self._ignore_unrecognized_tag = ignore_unrecognized_tag

self._fd = None
self._external_asdf_by_uri = {}
self._blocks = block.BlockManager(self)
Expand Down Expand Up @@ -428,7 +441,6 @@ def _find_asdf_version_in_comments(cls, comments):
def _open_asdf(cls, self, fd, uri=None, mode='r',
validate_checksums=False,
do_not_fill_defaults=False,
ignore_version_mismatch=False,
_get_yaml_content=False,
_force_raw_types=False):
"""Attempt to populate AsdfFile data from file-like object"""
Expand Down Expand Up @@ -466,7 +478,7 @@ def _open_asdf(cls, self, fd, uri=None, mode='r',
# We parse the YAML content into basic data structures
# now, but we don't do anything special with it until
# after the blocks have been read
tree = yamlutil.load_tree(reader, self, ignore_version_mismatch)
tree = yamlutil.load_tree(reader, self, self._ignore_version_mismatch)
has_blocks = fd.seek_until(constants.BLOCK_MAGIC, 4, include=True)
elif yaml_token == constants.BLOCK_MAGIC:
has_blocks = True
Expand All @@ -493,7 +505,6 @@ def _open_asdf(cls, self, fd, uri=None, mode='r',
def _open_impl(cls, self, fd, uri=None, mode='r',
validate_checksums=False,
do_not_fill_defaults=False,
ignore_version_mismatch=False,
_get_yaml_content=False,
_force_raw_types=False):
"""Attempt to open file-like object as either AsdfFile or AsdfInFits"""
Expand All @@ -505,6 +516,7 @@ def _open_impl(cls, self, fd, uri=None, mode='r',
from . import fits_embed
return fits_embed.AsdfInFits.open(fd, uri=uri,
validate_checksums=validate_checksums,
ignore_version_mismatch=self._ignore_version_mismatch,
extensions=self._extensions)
except ValueError:
pass
Expand All @@ -514,7 +526,6 @@ def _open_impl(cls, self, fd, uri=None, mode='r',
return cls._open_asdf(self, fd, uri=uri, mode=mode,
validate_checksums=validate_checksums,
do_not_fill_defaults=do_not_fill_defaults,
ignore_version_mismatch=ignore_version_mismatch,
_get_yaml_content=_get_yaml_content,
_force_raw_types=_force_raw_types)

Expand All @@ -523,7 +534,8 @@ def open(cls, fd, uri=None, mode='r',
validate_checksums=False,
extensions=None,
do_not_fill_defaults=False,
ignore_version_mismatch=False,
ignore_version_mismatch=True,
ignore_unrecognized_tag=False,
_force_raw_types=False):
"""
Open an existing ASDF file.
Expand Down Expand Up @@ -556,19 +568,25 @@ def open(cls, fd, uri=None, mode='r',

ignore_version_mismatch : bool, optional
When `True`, do not raise warnings for mismatched schema versions.
Set to `True` by default.

ignore_unrecognized_tag : bool, optional
When `True`, do not raise warnings for unrecognized tags. Set to
`False` by default.

Returns
-------
asdffile : AsdfFile
The new AsdfFile object.
"""
self = cls(extensions=extensions)
self = cls(extensions=extensions,
ignore_version_mismatch=ignore_version_mismatch,
ignore_unrecognized_tag=ignore_unrecognized_tag)

return cls._open_impl(
self, fd, uri=uri, mode=mode,
validate_checksums=validate_checksums,
do_not_fill_defaults=do_not_fill_defaults,
ignore_version_mismatch=ignore_version_mismatch,
_force_raw_types=_force_raw_types)

def _write_tree(self, tree, fd, pad_blocks):
Expand Down
62 changes: 47 additions & 15 deletions asdf/asdftypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def __init__(self):
self._unnamed_types = set()
self._hooks_by_type = {}
self._all_types = set()
self._has_warned = {}

def add_type(self, asdftype):
"""
Expand Down Expand Up @@ -215,7 +216,28 @@ def from_custom_type(self, custom_type, version='latest'):

return write_type_index.from_custom_type(custom_type)

def fix_yaml_tag(self, ctx, tag, ignore_version_mismatch=False):
def _get_version_mismatch(self, name, version, latest_version):
warning_string = None

if (latest_version.major, latest_version.minor) != \
(version.major, version.minor):
warning_string = \
"'{}' with version {} found in file{{}}, but latest " \
"supported version is {}".format(
name, version, latest_version)

return warning_string

def _warn_version_mismatch(self, ctx, tag, warning_string, fname):
if warning_string is not None:
# Ensure that only a single warning occurs per tag per AsdfFile
# TODO: If it is useful to only have a single warning per file on
# disk, then use `fname` in the key instead of `ctx`.
if not (ctx, tag) in self._has_warned:
warnings.warn(warning_string.format(fname))
self._has_warned[(ctx, tag)] = True

def fix_yaml_tag(self, ctx, tag, ignore_version_mismatch=True):
"""
Given a YAML tag, adjust it to the best supported version.

Expand All @@ -224,24 +246,37 @@ def fix_yaml_tag(self, ctx, tag, ignore_version_mismatch=False):
the earliest understood version if none are less than the
version in the file.

Raises a warning if it could not find a match where the major
and minor numbers are the same.
If ``ignore_version_mismatch==False``, this function raises a warning
if it could not find a match where the major and minor numbers are the
same.
"""
warning_string = None

name, version = split_tag_version(tag)

fname = " '{}'".format(ctx._fname) if ctx._fname else ''

if tag in self._type_by_tag:
asdftype = self._type_by_tag[tag]
# Issue warnings for the case where there exists a class for the
# given tag due to the 'supported_versions' attribute being
# defined, but this tag is not the latest version of the type.
# This prevents 'supported_versions' from affecting the behavior of
# warnings that are purely related to YAML validation.
if not ignore_version_mismatch and hasattr(asdftype, '_latest_version'):
warning_string = self._get_version_mismatch(
name, version, asdftype._latest_version)
self._warn_version_mismatch(ctx, tag, warning_string, fname)
return tag

fname = " '{}'".format(ctx._fname) if ctx._fname else ''
if tag in self._best_matches:
best_tag, warning_string = self._best_matches[tag]

if warning_string:
warnings.warn(warning_string.format(fname))
if not ignore_version_mismatch:
self._warn_version_mismatch(ctx, tag, warning_string, fname)

return best_tag

name, version = split_tag_version(tag)
versions = self._versions_by_type_name.get(name)
if versions is None:
return tag
Expand All @@ -251,16 +286,12 @@ def fix_yaml_tag(self, ctx, tag, ignore_version_mismatch=False):
i = bisect.bisect_left(versions, version)
i = max(0, i - 1)

best_version = versions[i]
if not ignore_version_mismatch:
if (best_version.major, best_version.minor) != \
(version.major, version.minor):
warning_string = \
"'{}' with version {} found in file{{}}, but latest " \
"supported version is {}".format(
name, version, best_version)
warnings.warn(warning_string.format(fname))
warning_string = self._get_version_mismatch(
name, version, versions[-1])
self._warn_version_mismatch(ctx, tag, warning_string, fname)

best_version = versions[i]
best_tag = join_tag_version(name, best_version)
self._best_matches[tag] = best_tag, warning_string
if tag != best_tag:
Expand Down Expand Up @@ -418,6 +449,7 @@ def __new__(mcls, name, bases, attrs):
new_attrs = copy(attrs)
new_attrs['version'] = version
new_attrs['supported_versions'] = set()
new_attrs['_latest_version'] = cls.version
siblings.append(
ExtensionTypeMeta. __new__(mcls, name, bases, new_attrs))
setattr(cls, '__versioned_siblings', siblings)
Expand Down
15 changes: 10 additions & 5 deletions asdf/fits_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,10 @@ class AsdfInFits(asdf.AsdfFile):
ff.write_to('test.fits') # doctest: +SKIP
"""

def __init__(self, hdulist=None, tree=None, uri=None, extensions=None):
def __init__(self, hdulist=None, tree=None, **kwargs):
if hdulist is None:
hdulist = fits.HDUList()
super(AsdfInFits, self).__init__(
tree=tree, uri=uri, extensions=extensions)
super(AsdfInFits, self).__init__(tree=tree, **kwargs)
self._blocks = _EmbeddedBlockManager(hdulist, self)
self._hdulist = hdulist
self._close_hdulist = False
Expand All @@ -162,7 +161,8 @@ def close(self):
self._tree = {}

@classmethod
def open(cls, fd, uri=None, validate_checksums=False, extensions=None):
def open(cls, fd, uri=None, validate_checksums=False, extensions=None,
ignore_version_mismatch=True, ignore_unrecognized_tag=False):
"""Creates a new AsdfInFits object based on given input data

Parameters
Expand All @@ -185,6 +185,9 @@ def open(cls, fd, uri=None, validate_checksums=False, extensions=None):
A list of extensions to the ASDF to support when reading
and writing ASDF files. See `asdftypes.AsdfExtension` for
more information.

ignore_version_mismatch : bool, optional
When `True`, do not raise warnings for mismatched schema versions.
"""
close_hdulist = False
if isinstance(fd, fits.hdu.hdulist.HDUList):
Expand All @@ -202,7 +205,9 @@ def open(cls, fd, uri=None, validate_checksums=False, extensions=None):
msg = "Failed to parse given file '{}'. Is it FITS?"
raise ValueError(msg.format(file_obj.uri))

self = cls(hdulist, uri=uri, extensions=extensions)
self = cls(hdulist, uri=uri, extensions=extensions,
ignore_version_mismatch=ignore_version_mismatch,
ignore_unrecognized_tag=ignore_unrecognized_tag)
self._close_hdulist = close_hdulist

try:
Expand Down
3 changes: 3 additions & 0 deletions asdf/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ def display_warnings(_warnings):
msg : str
String containing the warning messages to be displayed
"""
if len(_warnings) == 0:
return "No warnings occurred (was one expected?)"

msg = "Unexpected warning(s) occurred:\n"
for warning in _warnings:
msg += "{}:{}: {}: {}\n".format(
Expand Down
Loading