Example

In this example, we will show how to implement serialization for a new Model object, but the basic principles apply to serialization of other astropy objects. As mentioned, adding a new object to asdf-astropy requires both a tag and a converter.

Creating the Tag

All of the tags for transforms (astropy models) are currently defined within the asdf-transform-schemas, along side the schemas which compose them. Any new serializable astropy model will require the creation of a new tag, which will likely require the creation of a new schema.

Let’s consider a new model called MyModel, a new model in astropy.modeling.functional_models that has two parameters amplitude and x_0. We would like to strictly require both of these parameters be set. We would also like to specify that these parameters can either be numeric type, or astropy.units.quantity type. A schema describing this model would look like

%YAML 1.1
---
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
id: "http://astropy.org/schemas/mymodel-1.0.0"
title: >
  Example New Model.

description: >
  Example new model, which describes the distribution of ABC.

allOf:
  - $ref: "transform-1.2.0"
  - type: object
    properties:
      amplitude:
        anyOf:
          - tag: tag:stsci.edu:asdf/unit/quantity-1.1.0
          - type: number
        description: Amplitude of distribution.
      x_0:
        anyOf:
          - tag: tag:stsci.edu:asdf/unit/quantity-1.1.0
          - type: number
        description: X center position.

    required: ['amplitude', 'x_0]
...

All new transform schemas reference the base transform schema with the latest version. This schema describes the other model attributes that are common to all or many models, so that individual schemas only handle the parameters specific to that model. Additionally, this schema uses the latest tag for quantity, so that models can retain information about units and quantities. References allow previously defined schemas to be used inside new custom types, while the direct reference to a specific tag is preferred when possible as this allows ASDF to more confidently validate both the schema itself and the ASDF files which make use of it.

Finally, we can create the tag itself. This is done by creating an entry in a manifest for the tag. The manifest entry is where the tag gets associated with the schemas that are used by ASDF to validate the ASDF file. An example manifest entry for this model would look something like:

- tag_uri: tag:stsci.edu:asdf/transform/mymodel-1.0.0
  schema_uri: http://astropy.org/schemas/mymodel-1.0.0
  title: Example New Model
  description: |-
      Example new model, which describes the distribution of ABC.

If one was contributing this tag to asdf-astropy, this entry would be added the asdf-astropy Manifest directly. Doing this will allow asdf-astropy to properly register this tag and associate this tag with its underlying schema for use by ASDF. Moreover, the underlying schema will need to be added to the asdf_astropy/resources/schemas directly in order for asdf-astropy to make use of it when creating the tag in ASDF.

Note

This is not a complete manifest, instead it is a listing for a single tag for a manifest. See asdf-astropy Manifest for an example of a complete manifest. Moreover, a manifest is not strictly the only way to create a tag for ASDF (this can be done using the ASDF context manager for example); however, it is the standard way to create a tag for ASDF for use with a given package.

Creating a Converter

The next component for enabling ASDF to serialize and deserialize an object is to create a converter class.

Note

For most transforms the asdf_astropy.converters.transform.core.SimpleTransformConverter will be sufficient to construct the necessary converter for your model. However, for completeness we will describe the general procedure for writing both a transform converter and a more general converter.

Creating a Transform Converter

If we want to use the asdf-astropy framework for writing transform converters; namely, using asdf_astropy.converters.transform.core.TransformConverterBase, we need to define two methods to_yaml_tree_transform and from_yaml_tree_transform. The to_yaml_tree_transform will perform the serialization of the parts of MyModel which are specific to MyModel, while from_yaml_tree_transform will perform the deserialization of the parts of MyModel specific to MyModel. Moreover, the converter class must also specify the tags corresponding to MyModel and the matching Python types for those tags. The tags are what ASDF uses to identify which converter to use when deserializing an ASDF file, while the types are used by ASDF to identify which converter to use when serializing an object to an ASDF file.:

from asdf_astropy.converters.transform.core import TransformConverterBase, parameter_to_value

class MyModelConverter(TransformConverterBase):
    tags = ["tag:stsci.edu:asdf/transform/mymodel-1.0.0"]
    types = ['astropy.modeling.functional_models.MyModel']

    def to_yaml_tree_transform(self, model, tag, ctx):
        node = {'amplitude': parameter_to_value(amplitude),
                'x_0': parameter_to_value(x_0)}
        return node

    def from_yaml_tree_transform(self, node, tag, ctx):
        from astropy.modeling.functional_models import MyModel

        return MyModel(amplitude=node['amplitude'], x_0=node['x_0'])

If one was contributing this converter to asdf-astropy, this class would need to be instantiated and then added to the TRANSFORM_CONVERTERS list in the asdf_astropy.extensions module. By doing this asdf-astropy will be able to properly register this converter with ASDF so that it can be used seamlessly when working with ASDF.

Creating a General Converter

If one needs to create a more general (e.g. non-transform) converter, say MyType, then one will need to inherit from asdf.extension.Converter. In this case tags and types must still be defined, but instead to_yaml_tree and from_yaml_tree must be defined instead:

from asdf.extension import Converter

class MyTypeConverter(Converter):
    tags = ["tag:<tag for MyType"]
    types = ["<Python import for MyType>"]

    def to_yaml_tree(self, obj, tag, ctx):
        """Code to create a Python dictionary representing MyType"""
        ...

    def from_yaml_tree(self, node, tag, ctx):
        """Code to read a Python dictionary representing MyType"""
        ...

For more details please see Extensions.