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

DM-27477: Add JSON serialization for ObservationInfo #48

Merged
merged 7 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions python/astro_metadata_translator/observationGroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def __iter__(self):
def __eq__(self, other):
"""Compares equal if all the members are equal in the same order.
"""
if not isinstance(other, ObservationGroup):
return NotImplemented
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking into this, I have now learned about NotImplemented as a valid return for __eq__.


for info1, info2 in zip(self, other):
if info1 != info2:
return False
Expand Down Expand Up @@ -188,3 +191,30 @@ def property_values(self, property):
All the distinct values for that property within this group.
"""
return {getattr(obs_info, property) for obs_info in self}

def to_simple(self):
"""Convert the group to simplified form.

Returns
-------
simple : `list` of `dict`
Simple form is a list containing the simplified dict form of
each `ObservationInfo`.
"""
return [obsinfo.to_simple() for obsinfo in self]

@classmethod
def from_simple(cls, simple):
"""Convert simplified form back to `ObservationGroup`

Parameters
----------
simple : `list` of `dict`
Object returned by `to_simple`.

Returns
-------
group : `ObservationGroup`
Reconstructed group.
"""
return cls((ObservationInfo.from_simple(o) for o in simple))
116 changes: 102 additions & 14 deletions python/astro_metadata_translator/observationInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import itertools
import logging
import copy
import json

import astropy.time
from astropy.coordinates import SkyCoord, AltAz
Expand Down Expand Up @@ -267,19 +268,11 @@ def __str__(self):
def __eq__(self, other):
"""Compares equal if standard properties are equal
"""
if type(self) != type(other):
return False

for p in self._PROPERTIES:
# Use string comparison since SkyCoord.__eq__ seems unreliable
# otherwise. Should have per-type code so that floats and
# quantities can be compared properly.
v1 = f"{getattr(self, p)}"
v2 = f"{getattr(other, p)}"
if v1 != v2:
return False
if not isinstance(other, ObservationInfo):
return NotImplemented

return True
# Compare the simplified forms
return self.to_simple() == other.to_simple()

def __lt__(self, other):
return self.datetime_begin < other.datetime_begin
Expand Down Expand Up @@ -310,6 +303,100 @@ def __setstate__(self, state):
property = f"_{p}"
setattr(self, property, state[p])

def to_simple(self):
"""Convert the contents of this object to simple dict form.

The keys of the dict are the standard properties but the values
can be simplified to support JSON serialization. For example a
SkyCoord might be represented as an ICRS RA/Dec tuple rather than
a full SkyCoord representation.

Any properties with `None` value will be skipped.

Can be converted back to an `ObservationInfo` using `from_simple()`.

Returns
-------
simple : `dict` of [`str`, `Any`]
Simple dict of all properties.
"""
simple = {}

for p in self._PROPERTIES:
property = f"_{p}"
value = getattr(self, property)
if value is None:
continue

# Access the function to simplify the property
simplifier = self._PROPERTIES[p][3]

if simplifier is None:
simple[p] = value
continue

simple[p] = simplifier(value)

return simple

def to_json(self):
"""Serialize the object to JSON string.

Returns
-------
j : `str`
The properties of the ObservationInfo in JSON string form.
"""
return json.dumps(self.to_simple())

@classmethod
def from_simple(cls, simple):
"""Convert the entity returned by `to_simple` back into an
`ObservationInfo`.

Parameters
----------
simple : `dict` [`str`, `Any`]
The dict returned by `to_simple()`

Returns
-------
obsinfo : `ObservationInfo`
New object constructed from the dict.
"""
processed = {}
for k, v in simple.items():

if v is None:
continue

# Access the function to convert from simple form
complexifier = cls._PROPERTIES[k][4]

if complexifier is not None:
v = complexifier(v, **processed)

processed[k] = v

return cls.makeObservationInfo(**processed)

@classmethod
def from_json(cls, json_str):
"""Create `ObservationInfo` from JSON string.

Parameters
----------
json_str : `str`
The JSON representation.

Returns
-------
obsinfo : `ObservationInfo`
Reconstructed object.
"""
simple = json.loads(json_str)
return cls.from_simple(simple)

@classmethod
def makeObservationInfo(cls, **kwargs): # noqa: N802
"""Construct an `ObservationInfo` from the supplied parameters.
Expand Down Expand Up @@ -344,7 +431,8 @@ def makeObservationInfo(cls, **kwargs): # noqa: N802
unused.remove(p)

if unused:
raise KeyError(f"Unrecognized properties provided: {', '.join(unused)}")
n = len(unused)
raise KeyError(f"Unrecognized propert{'y' if n == 1 else 'ies'} provided: {', '.join(unused)}")

return obsinfo

Expand Down Expand Up @@ -386,7 +474,7 @@ def getter(self):
# getter methods.
for name, description in ObservationInfo._PROPERTIES.items():
setattr(ObservationInfo, f"_{name}", None)
setattr(ObservationInfo, name, property(_make_property(name, *description)))
setattr(ObservationInfo, name, property(_make_property(name, *description[:3])))


def makeObservationInfo(**kwargs): # noqa: N802
Expand Down