pax_global_header00006660000000000000000000000064145251562420014520gustar00rootroot0000000000000052 comment=395c51621d87eca9361fe012cf06e1a9ccc8198e asdf-astropy-0.5.0/000077500000000000000000000000001452515624200141365ustar00rootroot00000000000000asdf-astropy-0.5.0/.github/000077500000000000000000000000001452515624200154765ustar00rootroot00000000000000asdf-astropy-0.5.0/.github/workflows/000077500000000000000000000000001452515624200175335ustar00rootroot00000000000000asdf-astropy-0.5.0/.github/workflows/changelog.yml000066400000000000000000000015371452515624200222130ustar00rootroot00000000000000name: Changelog on: pull_request: types: [labeled, unlabeled, opened, synchronize, reopened] permissions: contents: read # Only cancel in-progress jobs or runs for the current workflow # This cancels the already triggered workflows for a specific PR without canceling # other instances of this workflow (other PRs, scheduled triggers, etc) when something # within that PR re-triggers this CI concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: changelog: name: Confirm changelog entry runs-on: ubuntu-latest steps: - name: Check change log entry uses: scientific-python/action-check-changelogfile@6087eddce1d684b0132be651a4dad97699513113 # 0.2 env: CHANGELOG_FILENAME: CHANGES.rst CHECK_MILESTONE: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} asdf-astropy-0.5.0/.github/workflows/ci.yml000066400000000000000000000077661452515624200206710ustar00rootroot00000000000000name: CI on: workflow_dispatch: schedule: # Run every Monday at 6am UTC - cron: '0 6 * * 1' push: branches: - main - '*.x' tags: - "*" pull_request: permissions: contents: read jobs: setup: runs-on: ubuntu-latest outputs: requirements-hash: ${{ steps.requirements-hash.outputs.hash }} steps: - uses: actions/checkout@v3 - id: requirements-hash run: echo "::set-output name=hash::${{ hashFiles('**/pyproject.toml', '**/setup.*', 'tox.ini') }}" core: needs: [setup] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.9' envs: | - linux: py39-parallel-cov - linux: py39-test-devdeps-parallel-cov - linux: py39-astropylts-parallel-cov - linux: py39-transformlts-parallel-cov coverage: codecov asdf-schemas: needs: [setup] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- default_python: '3.10' envs: | - linux: asdf - linux: asdf-standard - linux: asdf-transform-schemas - linux: asdf-coordinates-schemas test: needs: [core, asdf-schemas] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.10' envs: | - linux: py310-test-parallel - linux: py311-test-parallel - macos: py311-test-parallel - windows: py311-test-parallel dev: needs: [core, asdf-schemas] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.9' envs: | - linux: py310-test-devdeps-parallel - linux: py311-test-devdeps-parallel oldest: needs: [core, asdf-schemas] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.9' envs: | - linux: py39-test-oldestdep-parallels-cov coverage: codecov numpy: needs: [core, asdf-schemas] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: cache-path: ~/.cache/pip cache-key: pip-${{ needs.setup.outputs.requirements-hash }} cache-restore-keys: | pip- # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.9' envs: | - linux: py39-test-numpy119-parallel - linux: py39-test-numpy120-parallel - linux: py310-test-numpy121-parallel - linux: py310-test-numpy122-parallel - linux: py311-test-numpy123-parallel - linux: py311-test-numpy124-parallel wheel_building: permissions: contents: none uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1 if: (github.event_name == 'push' || github.event_name == 'pull_request') with: upload_to_pypi: false upload_to_anaconda: false test_extras: test test_command: pytest --pyargs asdf_astropy asdf-astropy-0.5.0/.github/workflows/downstream.yml000066400000000000000000000037761452515624200224560ustar00rootroot00000000000000name: Downstream on: workflow_dispatch: schedule: # Run every Monday at 6am UTC - cron: '0 6 * * 1' pull_request: # We also want this workflow triggered if the `Downstream CI` label is # added or present when PR is updated types: - synchronize - labeled push: branches: - main - '*.x' tags: - "*" permissions: contents: read jobs: astropy: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 if: (github.repository == 'astropy/asdf-astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Downstream CI'))) with: submodules: false # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.10' envs: | - linux: specutils stsci: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 if: (github.repository == 'astropy/asdf-astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Downstream CI'))) with: submodules: false # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.10' envs: | - linux: gwcs - linux: jwst - linux: stdatamodels - linux: roman_datamodels third-party: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 if: (github.repository == 'astropy/asdf-astropy' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Downstream CI'))) with: submodules: false # Any env name which does not start with `pyXY` will use this Python version. default_python: '3.10' envs: | - linux: sunpy - linux: dkist asdf-astropy-0.5.0/.github/workflows/publish-to-pypi.yml000066400000000000000000000007041452515624200233240ustar00rootroot00000000000000name: Release on: release: types: [released] permissions: contents: read jobs: build-n-publish: permissions: contents: none if: (github.event_name == 'release') uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1 with: upload_to_pypi: true test_extras: test test_command: pytest --pyargs asdf_astropy secrets: pypi_token: ${{ secrets.PYPI_PASSWORD }} asdf-astropy-0.5.0/.gitignore000066400000000000000000000011621452515624200161260ustar00rootroot00000000000000# Compiled files *.py[cod] *.a *.o *.so *.pyd *.dll __pycache__ # Other generated files MANIFEST asdf_astropy/_version.py coverage.xml .mypy_cache # Sphinx _build _generated docs/api docs/generated docs/visualization/ngc6976.jpeg docs/visualization/ngc6976-default.jpeg # Packages/installer info *.egg *.egg-info dist build eggs .eggs bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz .venv venv # Other .cache .tox .*.swp .*.swo *~ .project .pydevproject .settings .coverage cover htmlcov .hypothesis # Mac OSX .DS_Store # PyCharm .idea # Pytest v .pytest_cache # VSCode .vscode .tmp pip-wheel-metadata asdf-astropy-0.5.0/.pre-commit-config.yaml000066400000000000000000000026641452515624200204270ustar00rootroot00000000000000ci: autofix_prs: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast - id: check-case-conflict - id: check-yaml args: ["--unsafe"] - id: check-toml - id: check-merge-conflict - id: check-symlinks - id: debug-statements - id: detect-private-key - id: fix-encoding-pragma args: ["--remove"] - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - id: python-check-blanket-noqa - id: python-check-mock-methods - id: rst-directive-colons - id: rst-inline-touching-normal - id: text-unicode-replacement-char - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: - id: codespell args: ["--write-changes"] additional_dependencies: - tomli - repo: https://github.com/ikamensh/flynt/ rev: '1.0.1' hooks: - id: flynt - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.1.4" hooks: - id: ruff args: ["--fix"] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort - repo: https://github.com/psf/black rev: 23.10.1 hooks: - id: black - repo: https://github.com/asottile/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs - repo: https://github.com/abravalheri/validate-pyproject rev: "v0.15" hooks: - id: validate-pyproject asdf-astropy-0.5.0/.readthedocs.yaml000066400000000000000000000004271452515624200173700ustar00rootroot00000000000000version: 2 build: os: ubuntu-20.04 apt_packages: - graphviz tools: python: "3.9" sphinx: fail_on_warning: true configuration: docs/conf.py python: install: - method: pip path: . extra_requirements: - docs - all formats: [] asdf-astropy-0.5.0/CHANGES.rst000066400000000000000000000054371452515624200157510ustar00rootroot000000000000000.5.0 (2023-11-15) ------------------ - Drop support for Python 3.8 in accordance with NEP 29. [#180] - Update ``RepresentationConverter`` for new class paths in astropy [#181] - Update Converters so that all Class Variables are immutable [#188] - Remove ``oldest-supported-numpy`` from ``pyproject.toml`` ``build-system`` as this was never needed and will cause problems building on python 3.12 betas. [#193] - Use unique uri for extensions that implement converters for core asdf types [#199] - Add support for astropy.table.NdarrayMixin [#200] - Update angle converters for new class paths in astropy [#207] 0.4.0 (2023-03-20) ------------------ - Update pins for ``asdf``, ``asdf-coordinates-schemas``, ``numpy``, and ``packaging``. [#164] - Add serialization support for non-VOunits. [#142] - Add serialization support for ``MagUnit`` based units. [#146] - Document and add ``assert_model_roundtrip`` and ``assert_table_roundtrip`` to ``asdf_astropy.testing.helpers``. [#170] 0.3.0 (2022-11-29) ------------------ - Update citations. [#111] - Switch to using ``pyproject.toml`` for package configuration. [#106] - Fix bug with ``memmap`` of ``Quantity`` objects. [#125] - Drop support for ``numpy-1.18``. [#116] - Fix bug with ``str`` representations of ``astropy.time`` objects. [#132] - Fix bug in preserving the ``dtype`` of ``Quantity`` objects. [#131] 0.2.2 (2022-08-22) ------------------ - Add converter for the new ``Schechter1D`` model. [#67] - Add CITATION file. [#71] - Add migration and quick-start documentation guides, and update minimum Python version [#77] - Update ``FrameConverter`` to enable the use of multiple tags. [#81] - Bugfixes for ``astropy.time`` converters. [#86] - Remove unnecessary ``tag:`` from schemas. [#103] - Add converters for ``ModelBoundingBox`` and ``CompoundBoundingBox``. [#69] 0.2.1 (2022-04-18) ------------------ - Migrate documentation from ``astropy`` to ``asdf-astropy``. [#55] - Pin astropy min version to 5.0.4. [#62] 0.2.0 (2022-03-08) ------------------ - Add support for serialization and deserialization of input_units_equivalencies for astropy models. [#37] - Bugfix for units_mapping schema's property name conflicts. Changes: - ``inputs`` to ``unit_inputs`` - ``outputs`` to ``unit_outputs`` [#39] - Add converter support for Cosine1D, Tangent1D, ArcSine1D, ArcCosine1D, ArcTangent1D models. [#42] - Add converter for Spline1D model. [#43] - Add astropy Table connector for ASDF. [#47] - Move assert_model_equal to helpers module. [#50] - Fix warnings raised during testing. [#52] 0.1.2 (2021-12-14) ------------------ - Fix bug in Table deserializer when meta is absent from the ASDF. [#36] 0.1.1 (2021-12-04) ------------------ - Retrieve coordinates schemas from asdf-coordinates-schemas. [#35] 0.1.0 (2021-12-01) ------------------ - Initial release. asdf-astropy-0.5.0/CITATION000066400000000000000000000042251452515624200152760ustar00rootroot00000000000000If you use asdf-astropy for work/research presented in a publication (whether directly, as a dependency to another package), we recommend and encourage you to cite the general ASDF paper and or the update paper. The BibText entry for the citations are: @article{GREENFIELD2015240, title = {ASDF: A new data format for astronomy}, journal = {Astronomy and Computing}, volume = {12}, pages = {240-251}, year = {2015}, issn = {2213-1337}, doi = {https://doi.org/10.1016/j.ascom.2015.06.004}, url = {https://www.sciencedirect.com/science/article/pii/S2213133715000645}, author = {P. Greenfield and M. Droettboom and E. Bray}, keywords = {FITS, File formats, Standards, World coordinate system}, abstract = {We present the case for developing a successor format for the immensely successful FITS format. We first review existing alternative formats and discuss why we do not believe they provide an adequate solution. The proposed format is called the Advanced Scientific Data Format (ASDF) and is based on an existing text format, YAML, that we believe removes most of the current problems with the FITS format. An overview of the capabilities of the new format is given along with specific examples. This format has the advantage that it does not limit the size of attribute names (akin to FITS keyword names) nor place restrictions on the size or type of values attributes have. Hierarchical relationships are explicit in the syntax and require no special conventions. Finally, it is capable of storing binary data within the file in its binary form. At its basic level, the format proposed has much greater applicability than for just astronomical data.} } @InProceedings{ 00_greenfield-proc-scipy-2022, author = { {P}erry {G}reenfield and {E}dward {S}lavich and {W}illiam {J}amieson and {N}adia {D}encheva }, title = { {T}he {A}dvanced {S}cientific {D}ata {F}ormat ({A}{S}{D}{F}): {A}n {U}pdate }, booktitle = { {P}roceedings of the 21st {P}ython in {S}cience {C}onference }, pages = { 1 - 6 }, year = { 2022 }, editor = { {M}eghann {A}garwal and {C}hris {C}alloway and {D}illon {N}iederhut and {D}avid {S}hupe }, doi = { 10.25080/majora-212e5952-000 } } asdf-astropy-0.5.0/LICENSE.rst000066400000000000000000000027301452515624200157540ustar00rootroot00000000000000Copyright (c) 2011-2022, Astropy Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. asdf-astropy-0.5.0/MANIFEST.in000066400000000000000000000001031452515624200156660ustar00rootroot00000000000000prune docs/_build prune docs/api global-exclude *.pyc exclude .* asdf-astropy-0.5.0/README.rst000066400000000000000000000077101452515624200156320ustar00rootroot00000000000000ASDF serialization support for astropy -------------------------------------- .. image:: https://github.com/astropy/asdf-astropy/actions/workflows/ci.yml/badge.svg :target: https://github.com/astropy/asdf-astropy/actions :alt: CI Status .. image:: https://codecov.io/gh/astropy/asdf-astropy/branch/main/graph/badge.svg?token=0XGOYX4QGT :target: https://codecov.io/gh/astropy/asdf-astropy :alt: Code coverage .. image:: https://github.com/astropy/asdf-astropy/workflows/Downstream/badge.svg :target: https://github.com/astropy/asdf-astropy/actions :alt: Downstream CI Status .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat :target: http://www.astropy.org :alt: Powered by Astropy Badge .. image:: https://readthedocs.org/projects/asdf-astropy/badge/?version=latest :target: https://asdf-astropy.readthedocs.io/en/latest/ .. image:: https://zenodo.org/badge/271820376.svg :target: https://zenodo.org/badge/latestdoi/271820376 .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white :target: https://github.com/pre-commit/pre-commit :alt: pre-commit .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black .. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336 :target: https://pycqa.github.io/isort/ This package includes plugins that provide ASDF serialization support for astropy objects. The plugins are automatically enabled when the package is installed. The plugins in this package supersede those in the ``astropy.io.misc.asdf`` module; when this package is installed, the astropy plugins will be ignored. The ``astropy.io.misc.asdf`` module will be removed in a future version of astropy. License ------- This project is Copyright (c) Association of Universities for Research in Astronomy (AURA) and licensed under the terms of the BSD 3-Clause license. This package is based upon the `Astropy package template `_ which is licensed under the BSD 3-clause license. See the licenses folder for more information. Installation ------------ .. _begin-pip-install-text: Stable releases of the asdf-astropy python package are registered `at PyPi `__. The latest stable version can be installed using ``pip``: .. code-block:: console $ pip install asdf-astropy .. _end-pip-install-text: .. _begin-source-install-text: The latest development version of asdf-astropy is available from the ``main`` branch `on github `__. To clone the project: .. code-block:: console $ git clone https://github.com/astropy/asdf-astropy To install: .. code-block:: console $ cd asdf-astropy $ pip install . To install in `development mode `__ .. code-block:: console $ pip install -e . .. _end-source-install-text: Testing ------- .. _begin-testing-text: To install the test dependencies from a source checkout of the repository: .. code-block:: console $ pip install -e ".[test]" To run the unit tests from a source checkout of the repository: .. code-block:: console $ pytest It is also possible to run the test suite from an installed version of the package. .. code-block:: console $ pip install "asdf-astropy[test]" $ pytest --pyargs asdf-astropy It is also possible to run the tests using `tox `__. .. code-block:: console $ pip install tox To list all available environments: .. code-block:: console $ tox -va To run a specific environment: .. code-block:: console $ tox -e .. _end-testing-text: Contributing ------------ We love contributions! asdf-astropy is open source, built on open source, and we'd love to have you hang out in our community. asdf-astropy-0.5.0/asdf_astropy/000077500000000000000000000000001452515624200166345ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/__init__.py000066400000000000000000000004511452515624200207450ustar00rootroot00000000000000# Packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * # noqa: F403 # ---------------------------------------------------------------------------- asdf-astropy-0.5.0/asdf_astropy/_astropy_init.py000066400000000000000000000003761452515624200220770ustar00rootroot00000000000000from pathlib import Path from astropy.tests.runner import TestRunner __all__ = ["__version__", "test"] from ._version import version as __version__ # Create the test function for self test test = TestRunner.make_test_runner_in(Path(__file__).parent) asdf-astropy-0.5.0/asdf_astropy/_manifest.py000066400000000000000000000022741452515624200211600ustar00rootroot00000000000000import re from itertools import chain from asdf import extension class CompoundManifestExtension(extension.Extension): """ Combine a listed of asdf ``ManifestExtensions`` into a single extension. """ def __init__(self, extensions): self._extensions = extensions # overwrite extension uri from first extension (the one from asdf-standard) # so that this extension uses a new, unique uri self._extension_uri = re.sub("asdf-format", "astropy", extensions[0].extension_uri) @property def extension_uri(self): return self._extension_uri @property def asdf_standard_requirement(self): return self._extensions[0].asdf_standard_requirement @property def legacy_class_names(self): return list(chain.from_iterable(e.legacy_class_names for e in self._extensions)) @property def converters(self): return list(chain.from_iterable(e.converters for e in self._extensions)) @property def compressors(self): return list(chain.from_iterable(e.compressors for e in self._extensions)) @property def tags(self): return list(chain.from_iterable(e.tags for e in self._extensions)) asdf-astropy-0.5.0/asdf_astropy/conftest.py000066400000000000000000000000001452515624200210210ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/000077500000000000000000000000001452515624200210265ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/__init__.py000066400000000000000000000040661452515624200231450ustar00rootroot00000000000000__all__ = [ "AngleConverter", "LatitudeConverter", "LongitudeConverter", "EarthLocationConverter", "FrameConverter", "LegacyICRSConverter", "RepresentationConverter", "SkyCoordConverter", "SpectralCoordConverter", "EquivalencyConverter", "QuantityConverter", "UnitConverter", "MagUnitConverter", "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", "ColumnConverter", "AstropyTableConverter", "AsdfTableConverter", "NdarrayMixinConverter", "TimeDeltaConverter", "TimeConverter", "CompoundConverter", "TransformConverterBase", "SimpleTransformConverter", "ConstantConverter", "IdentityConverter", "RemapAxesConverter", "UnitsMappingConverter", "MathFunctionsConverter", "PolynomialConverter", "OrthoPolynomialConverter", "ProjectionConverter", "ModelBoundingBoxConverter", "CompoundBoundingBoxConverter", "Rotate3DConverter", "RotationSequenceConverter", "SplineConverter", "TabularConverter", ] from .coordinates import ( AngleConverter, EarthLocationConverter, FrameConverter, LatitudeConverter, LegacyICRSConverter, LongitudeConverter, RepresentationConverter, SkyCoordConverter, SpectralCoordConverter, ) from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter from .table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .time import TimeConverter, TimeDeltaConverter from .transform import ( CompoundBoundingBoxConverter, CompoundConverter, ConstantConverter, IdentityConverter, MathFunctionsConverter, ModelBoundingBoxConverter, OrthoPolynomialConverter, PolynomialConverter, ProjectionConverter, RemapAxesConverter, Rotate3DConverter, RotationSequenceConverter, SimpleTransformConverter, SplineConverter, TabularConverter, TransformConverterBase, UnitsMappingConverter, ) from .unit import EquivalencyConverter, MagUnitConverter, QuantityConverter, UnitConverter asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/000077500000000000000000000000001452515624200233405ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/__init__.py000066400000000000000000000011001452515624200254410ustar00rootroot00000000000000__all__ = [ "AngleConverter", "LatitudeConverter", "LongitudeConverter", "EarthLocationConverter", "FrameConverter", "LegacyICRSConverter", "RepresentationConverter", "SkyCoordConverter", "SpectralCoordConverter", ] from .angle import AngleConverter, LatitudeConverter, LongitudeConverter from .earth_location import EarthLocationConverter from .frame import FrameConverter, LegacyICRSConverter from .representation import RepresentationConverter from .sky_coord import SkyCoordConverter from .spectral_coord import SpectralCoordConverter asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/angle.py000066400000000000000000000026321452515624200250030ustar00rootroot00000000000000from asdf_astropy.converters.unit.quantity import QuantityConverter class AngleConverter(QuantityConverter): tags = ("tag:astropy.org:astropy/coordinates/angle-*",) types = ( "astropy.coordinates.angles.Angle", "astropy.coordinates.angles.core.Angle", ) def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.angles import Angle return Angle(super().from_yaml_tree(node, tag, ctx)) class LatitudeConverter(QuantityConverter): tags = ("tag:astropy.org:astropy/coordinates/latitude-*",) types = ( "astropy.coordinates.angles.Latitude", "astropy.coordinates.angles.core.Latitude", ) def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.angles import Latitude return Latitude(super().from_yaml_tree(node, tag, ctx)) class LongitudeConverter(QuantityConverter): tags = ("tag:astropy.org:astropy/coordinates/longitude-*",) types = ( "astropy.coordinates.angles.Longitude", "astropy.coordinates.angles.core.Longitude", ) def to_yaml_tree(self, obj, tag, ctx): tree = super().to_yaml_tree(obj, tag, ctx) tree["wrap_angle"] = obj.wrap_angle return tree def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.angles import Longitude return Longitude(super().from_yaml_tree(node, tag, ctx), wrap_angle=node["wrap_angle"]) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/earth_location.py000066400000000000000000000007171452515624200267120ustar00rootroot00000000000000from asdf.extension import Converter class EarthLocationConverter(Converter): tags = ("tag:astropy.org:astropy/coordinates/earthlocation-*",) types = ("astropy.coordinates.earth.EarthLocation",) def to_yaml_tree(self, obj, tag, ctx): return obj.info._represent_as_dict() def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.earth import EarthLocation return EarthLocation.info._construct_from_dict(node) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/frame.py000066400000000000000000000046651452515624200250170ustar00rootroot00000000000000from asdf.extension import Converter from asdf_astropy.converters.utils import import_type class FrameConverter(Converter): def __init__(self, tags, frame_type_name): self._frame_type_name = frame_type_name self._frame_type = None if isinstance(tags, str): tags = [tags] self._tags = tags @property def tags(self): return self._tags @property def types(self): return [self._frame_type_name] @property def frame_type(self): # Delay import until the frame class is needed to improve speed # of loading the extension. if self._frame_type is None: self._frame_type = import_type(self._frame_type_name) return self._frame_type def to_yaml_tree(self, obj, tag, ctx): node = {} if obj.has_data: node["data"] = obj.data # TODO: Figure out why we can't use the frame_attributes # values and document. frame_attributes = {} for attr in obj.frame_attributes: value = getattr(obj, attr, None) if value is not None: frame_attributes[attr] = value node["frame_attributes"] = frame_attributes return node def from_yaml_tree(self, node, tag, ctx): data = node.get("data", None) if data is not None: return self.frame_type(node["data"], **node["frame_attributes"]) return self.frame_type(**node["frame_attributes"]) class LegacyICRSConverter(Converter): tags = ("tag:astropy.org:astropy/coordinates/frames/icrs-1.0.0",) # Leave the types list empty so that the 1.1.0 ICRS converter # is used on write. types = () def to_yaml_tree(self, obj, tag, ctx): from astropy.units import Quantity return { "ra": { "value": obj.ra.value, "unit": obj.ra.unit.to_string(), "wrap_angle": Quantity(obj.ra.wrap_angle), }, "dec": { "value": obj.dec.value, "unit": obj.dec.unit.to_string(), }, } def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates import ICRS, Angle, Latitude, Longitude ra = Longitude(node["ra"]["value"], unit=node["ra"]["unit"], wrap_angle=Angle(node["ra"]["wrap_angle"])) dec = Latitude(node["dec"]["value"], unit=node["dec"]["unit"]) return ICRS(ra=ra, dec=dec) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/representation.py000066400000000000000000000055211452515624200267570ustar00rootroot00000000000000from asdf.extension import Converter class RepresentationConverter(Converter): tags = ("tag:astropy.org:astropy/coordinates/representation-*",) types = ( "astropy.coordinates.representation.CartesianDifferential", "astropy.coordinates.representation.CartesianRepresentation", "astropy.coordinates.representation.CylindricalDifferential", "astropy.coordinates.representation.CylindricalRepresentation", "astropy.coordinates.representation.PhysicsSphericalDifferential", "astropy.coordinates.representation.PhysicsSphericalRepresentation", "astropy.coordinates.representation.RadialDifferential", "astropy.coordinates.representation.RadialRepresentation", "astropy.coordinates.representation.SphericalCosLatDifferential", "astropy.coordinates.representation.SphericalDifferential", "astropy.coordinates.representation.SphericalRepresentation", "astropy.coordinates.representation.UnitSphericalCosLatDifferential", "astropy.coordinates.representation.UnitSphericalDifferential", "astropy.coordinates.representation.UnitSphericalRepresentation", # classes were moved in https://github.com/astropy/astropy/pull/14792 "astropy.coordinates.representation.cartesian.CartesianDifferential", "astropy.coordinates.representation.cartesian.CartesianRepresentation", "astropy.coordinates.representation.cylindrical.CylindricalDifferential", "astropy.coordinates.representation.cylindrical.CylindricalRepresentation", "astropy.coordinates.representation.spherical.PhysicsSphericalDifferential", "astropy.coordinates.representation.spherical.PhysicsSphericalRepresentation", "astropy.coordinates.representation.spherical.RadialDifferential", "astropy.coordinates.representation.spherical.RadialRepresentation", "astropy.coordinates.representation.spherical.SphericalCosLatDifferential", "astropy.coordinates.representation.spherical.SphericalDifferential", "astropy.coordinates.representation.spherical.SphericalRepresentation", "astropy.coordinates.representation.spherical.UnitSphericalRepresentation", "astropy.coordinates.representation.spherical.UnitSphericalDifferential", "astropy.coordinates.representation.spherical.UnitSphericalCosLatDifferential", ) def to_yaml_tree(self, obj, tag, ctx): components = {} for c in obj.components: value = getattr(obj, "_" + c, None) if value is not None: components[c] = value return { "type": type(obj).__name__, "components": components, } def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates import representation return getattr(representation, node["type"])(**node["components"]) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/sky_coord.py000066400000000000000000000007101452515624200257040ustar00rootroot00000000000000from asdf.extension import Converter class SkyCoordConverter(Converter): tags = ("tag:astropy.org:astropy/coordinates/skycoord-*",) types = ("astropy.coordinates.sky_coordinate.SkyCoord",) def to_yaml_tree(self, obj, tag, ctx): return obj.info._represent_as_dict() def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.sky_coordinate import SkyCoord return SkyCoord.info._construct_from_dict(node) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/spectral_coord.py000066400000000000000000000021571452515624200267220ustar00rootroot00000000000000from asdf.extension import Converter from asdf.tags.core.ndarray import NDArrayType class SpectralCoordConverter(Converter): tags = ("tag:astropy.org:astropy/coordinates/spectralcoord-*",) types = ("astropy.coordinates.spectral_coordinate.SpectralCoord",) def to_yaml_tree(self, obj, tag, ctx): node = { "value": obj.value, "unit": obj.unit, } if obj.observer is not None: node["observer"] = obj.observer if obj.target is not None: node["target"] = obj.target return node def from_yaml_tree(self, node, tag, ctx): from astropy.coordinates.spectral_coordinate import SpectralCoord value = node["value"] if isinstance(value, NDArrayType): # TODO: Why doesn't NDArrayType work? This needs some research # and documentation. See similar note in QuantityConverter. value = value._make_array() return SpectralCoord( value, unit=node["unit"], observer=node.get("observer"), target=node.get("target"), ) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/000077500000000000000000000000001452515624200245025ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/__init__.py000066400000000000000000000000001452515624200266010ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_angle.py000066400000000000000000000013531452515624200272030ustar00rootroot00000000000000import asdf import numpy as np import pytest from astropy import units as u from astropy.coordinates import Angle, Latitude, Longitude def create_angles(): return [ Angle(100, u.deg), Angle([100, 120, 150], u.deg), Angle([[90, 100, 110], [100, 120, 150]], u.deg), Angle(np.arange(100).reshape(5, 2, 10), u.deg), Latitude(10, u.deg), Longitude(-100, u.deg, wrap_angle=180 * u.deg), ] @pytest.mark.parametrize("angle", create_angles()) def test_serialization(angle, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["angle"] = angle af.write_to(file_path) with asdf.open(file_path) as af: assert (af["angle"] == angle).all() asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_earth_location.py000066400000000000000000000036071452515624200311140ustar00rootroot00000000000000import asdf import pytest from astropy import units as u from astropy.coordinates import EarthLocation, Latitude, Longitude from astropy.coordinates.earth import ELLIPSOIDS from astropy.units import Quantity from asdf_astropy.testing.helpers import assert_earth_location_equal def create_earth_locations(): longitude = Longitude([0.0, 45.0, 90.0, 135.0, 180.0, -180, -90, -45], u.deg, wrap_angle=180 * u.deg) latitude = Latitude([+0.0, 30.0, 60.0, +90.0, -90.0, -60.0, -30.0, 0.0], u.deg) height = Quantity([0.1, 0.5, 1.0, -0.5, -1.0, +4.2, -11.0, -0.1], u.m) position = (longitude, latitude, height) result = [ EarthLocation(lat=34.4900 * u.deg, lon=-104.221800 * u.deg, height=40 * u.km), EarthLocation(*EarthLocation.from_geodetic(*position).to_geocentric()), ] result.extend([EarthLocation.from_geodetic(*position, ellipsoid=e) for e in ELLIPSOIDS]) return result @pytest.mark.parametrize("earth_location", create_earth_locations()) def test_serialization(earth_location, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["earth_location"] = earth_location af.write_to(file_path) with asdf.open(file_path) as af: assert (af["earth_location"] == earth_location).all() @pytest.fixture() def _builtin_site_registry(): orig_sites = getattr(EarthLocation, "_site_registry", None) EarthLocation._get_site_registry(force_builtin=True) yield EarthLocation._site_registry = orig_sites @pytest.mark.usefixtures("_builtin_site_registry") def test_earthlocation_site(tmp_path): earth_location = EarthLocation.of_site("greenwich") file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["earth_location"] = earth_location af.write_to(file_path) with asdf.open(file_path) as af: assert_earth_location_equal(af["earth_location"], earth_location) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_frame.py000066400000000000000000000066041452515624200272130ustar00rootroot00000000000000import unittest.mock as mk import asdf import numpy as np import pytest from asdf.testing.helpers import yaml_to_asdf from astropy import units as u from astropy.coordinates import ( CIRS, FK4, FK5, GCRS, ICRS, ITRS, Angle, CartesianRepresentation, FK4NoETerms, Galactic, Galactocentric, Latitude, Longitude, PrecessedGeocentric, SphericalRepresentation, ) from astropy.time import Time from asdf_astropy.converters.coordinates.frame import FrameConverter, LegacyICRSConverter from asdf_astropy.testing.helpers import assert_frame_equal def create_frames(): test_data = { "ra": 1 * u.deg, "dec": 2 * u.deg, } return [ CIRS(), CIRS(**test_data), CIRS(**test_data, obstime=Time("J2005")), FK4(), FK4(**test_data), FK4(**test_data, obstime=Time("B1950")), FK4NoETerms(), FK4NoETerms(**test_data), FK4NoETerms(**test_data, obstime=Time("J1975")), FK5(), FK5(**test_data), FK5(**test_data, equinox="J2005"), FK5(**test_data, equinox="2011-01-01T00:00:00"), Galactic(l=47.37 * u.degree, b=+6.32 * u.degree), Galactocentric( x=np.linspace(-10.0, 10.0, 100) * u.kpc, y=np.linspace(-10.0, 10.0, 100) * u.kpc, z=np.zeros(100) * u.kpc, z_sun=15 * u.pc, ), GCRS(), GCRS(**test_data), GCRS(**test_data, obsgeoloc=CartesianRepresentation([1, 2, 3], unit=u.m)), ICRS(**test_data), ICRS(ra=Longitude(25, unit=u.deg), dec=Latitude(45, unit=u.deg)), ICRS(ra=Longitude(25, unit=u.deg, wrap_angle=Angle(1.5, unit=u.rad)), dec=Latitude(45, unit=u.deg)), ICRS(ra=[0, 1, 2] * u.deg, dec=[3, 4, 5] * u.deg), ITRS(SphericalRepresentation(lon=12.3 * u.deg, lat=45.6 * u.deg, distance=1 * u.km)), PrecessedGeocentric(), PrecessedGeocentric(**test_data), PrecessedGeocentric(**test_data, equinox="B1975"), ] @pytest.mark.parametrize("frame", create_frames()) def test_serialization(frame, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["frame"] = frame af.write_to(file_path) with asdf.open(file_path) as af: assert_frame_equal(af["frame"], frame) def test_tags(): converter = FrameConverter(["tag1", "tag2"], "test") assert converter.tags == ["tag1", "tag2"] def test_legacy_icrs_serialize(): converter = LegacyICRSConverter() ra = 25 dec = 45 frame = ICRS(ra=Longitude(ra, unit=u.deg), dec=Latitude(dec, unit=u.deg)) node = converter.to_yaml_tree(frame, mk.MagicMock(), mk.MagicMock()) assert node["ra"]["value"] == ra assert node["ra"]["unit"] == "deg" assert node["ra"]["wrap_angle"] == 360 * u.deg assert node["dec"]["value"] == dec assert node["dec"]["unit"] == "deg" def test_legacy_icrs_deseialize(): example = """! ra: value: 25 unit: deg wrap_angle: !unit/quantity-1.1.0 value: 360 unit: deg dec: value: 45 unit: deg""" truth = ICRS(ra=Longitude(25, unit=u.deg), dec=Latitude(45, unit=u.deg)) buff = yaml_to_asdf(f"example: {example.strip()}") with asdf.AsdfFile() as af: af._open_impl(af, buff, mode="rw") assert_frame_equal(af["example"], truth) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_representation.py000066400000000000000000000022641452515624200311610ustar00rootroot00000000000000import asdf import astropy.units as u import pytest from astropy.coordinates import Angle, representation from numpy.random import random from asdf_astropy.testing.helpers import assert_representation_equal IGNORED_REPRESENTATION_CLASSES = [ "WGS84GeodeticRepresentation", "WGS72GeodeticRepresentation", "GRS80GeodeticRepresentation", ] REPRESENTATION_CLASSES = [ getattr(representation, class_name) for class_name in representation.__all__ if "Base" not in class_name and class_name not in IGNORED_REPRESENTATION_CLASSES ] def create_representation(rep_class): kwargs = {} for attr_name, attr_type in rep_class.attr_classes.items(): value = random((100,)) * u.deg if issubclass(attr_type, Angle) else random((100,)) * u.km kwargs[attr_name] = value return rep_class(**kwargs) @pytest.mark.parametrize("rep_class", REPRESENTATION_CLASSES) def test_serialization(rep_class, tmp_path): rep = create_representation(rep_class) file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["rep"] = rep af.write_to(file_path) with asdf.open(file_path) as af: assert_representation_equal(af["rep"], rep) asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_sky_coord.py000066400000000000000000000052651452515624200301170ustar00rootroot00000000000000import asdf import astropy.units as u import numpy as np import pytest from astropy.coordinates import FK4, ICRS, Galactic, Longitude, SkyCoord from asdf_astropy.testing.helpers import assert_sky_coord_equal def create_sky_coords(): # These are cribbed directly from the Examples section of # https://docs.astropy.org/en/stable/api/astropy.coordinates.SkyCoord.html return [ # Defaults to ICRS frame SkyCoord(10, 20, unit="deg"), # Vector of 3 coords SkyCoord([1, 2, 3], [-30, 45, 8], frame="icrs", unit="deg"), # FK4 frame SkyCoord( ["1:12:43.2 +1:12:43", "1 12 43.2 +1 12 43"], frame=FK4, unit=(u.deg, u.hourangle), obstime="J1992.21", ), # Galactic frame SkyCoord("1h12m43.2s +1d12m43s", frame=Galactic), SkyCoord(frame="galactic", l="1h12m43.2s", b="+1d12m43s"), # With ra and dec SkyCoord( Longitude([1, 2, 3], unit=u.deg), np.array([4.5, 5.2, 6.3]) * u.deg, frame="icrs", ), SkyCoord( frame=ICRS, ra=Longitude([1, 2, 3], unit=u.deg), dec=np.array([4.5, 5.2, 6.3]) * u.deg, obstime="2001-01-02T12:34:56", ), # With overridden frame defaults SkyCoord(FK4(1 * u.deg, 2 * u.deg), obstime="J2010.11", equinox="B1965"), # Cartesian SkyCoord(w=0, u=1, v=2, unit="kpc", frame="galactic", representation_type="cartesian"), # Vector frame SkyCoord([ICRS(ra=1 * u.deg, dec=2 * u.deg), ICRS(ra=3 * u.deg, dec=4 * u.deg)]), # 2D obstime SkyCoord([1, 2], [3, 4], [5, 6], unit="deg,deg,m", frame="fk4", obstime=["J1990.5", "J1991.5"]), # Radial velocity SkyCoord(ra=1 * u.deg, dec=2 * u.deg, radial_velocity=10 * u.km / u.s), # Proper motion SkyCoord(ra=1 * u.deg, dec=2 * u.deg, pm_ra_cosdec=2 * u.mas / u.yr, pm_dec=1 * u.mas / u.yr), ] @pytest.mark.parametrize("coord", create_sky_coords()) def test_serialization(coord, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["coord"] = coord af.write_to(file_path) with asdf.open(file_path) as af: assert_sky_coord_equal(af["coord"], coord) def test_serializaton_extra_attribute(tmp_path): coord = SkyCoord(10 * u.deg, 20 * u.deg, equinox="2011-01-01T00:00", frame="fk4").transform_to("icrs") file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["coord"] = coord af.write_to(file_path) with asdf.open(file_path) as af: assert_sky_coord_equal(af["coord"], coord) assert hasattr(af["coord"], "equinox") asdf-astropy-0.5.0/asdf_astropy/converters/coordinates/tests/test_spectral_coord.py000066400000000000000000000024231452515624200311170ustar00rootroot00000000000000import warnings import asdf import pytest from astropy import units as u from astropy.coordinates import ICRS, Galactic, SpectralCoord from astropy.coordinates.spectral_coordinate import NoVelocityWarning from asdf_astropy.testing.helpers import assert_spectral_coord_equal def create_spectral_coords(): result = [ # Scalar SpectralCoord(565 * u.nm), # Vector SpectralCoord([100, 200, 300] * u.GHz), ] with warnings.catch_warnings(): warnings.simplefilter("ignore", NoVelocityWarning) result.append( # With observer and target SpectralCoord( 10 * u.GHz, observer=ICRS(1 * u.km, 2 * u.km, 3 * u.km, representation_type="cartesian"), target=Galactic(10 * u.deg, 20 * u.deg, distance=30 * u.pc), ), ) return result @pytest.mark.filterwarnings("ignore::astropy.coordinates.spectral_coordinate.NoVelocityWarning") @pytest.mark.parametrize("coord", create_spectral_coords()) def test_serialization(coord, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["coord"] = coord af.write_to(file_path) with asdf.open(file_path) as af: assert_spectral_coord_equal(af["coord"], coord) asdf-astropy-0.5.0/asdf_astropy/converters/fits/000077500000000000000000000000001452515624200217735ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/fits/__init__.py000066400000000000000000000002421452515624200241020ustar00rootroot00000000000000__all__ = [ "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", ] from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter asdf-astropy-0.5.0/asdf_astropy/converters/fits/fits.py000066400000000000000000000040251452515624200233130ustar00rootroot00000000000000from asdf.extension import Converter from asdf.tags.core.ndarray import NDArrayType def _card_to_node(card): from astropy.io.fits import Undefined value = "" if isinstance(card.value, Undefined) else card.value if card.comment: return [card.keyword, value, card.comment] if value: return [card.keyword, value] if card.keyword: return [card.keyword] return [] class FitsConverter(Converter): def to_yaml_tree(self, obj, tag, ctx): from astropy.table import Table node = [] for hdu in obj: header_node = [_card_to_node(c) for c in hdu.header.cards] hdu_node = {"header": header_node} if hdu.data is not None: if hdu.data.dtype.names is not None: hdu_node["data"] = Table(hdu.data) else: hdu_node["data"] = hdu.data node.append(hdu_node) return node def from_yaml_tree(self, node, tag, ctx): from astropy.io import fits hdus = [] first = True for hdu_node in node: header = fits.Header([fits.Card(*x) for x in hdu_node["header"]]) data = hdu_node.get("data") if isinstance(data, NDArrayType): # TODO: Why doesn't NDArrayType work? This needs some research # and documentation. data = data._make_array() if first: hdu = fits.PrimaryHDU(data=data, header=header) first = False elif data.dtype.names is not None: hdu = fits.BinTableHDU(data=data, header=header) else: hdu = fits.ImageHDU(data=data, header=header) hdus.append(hdu) return fits.HDUList(hdus) class AsdfFitsConverter(FitsConverter): tags = ("tag:stsci.edu:asdf/fits/fits-*",) types = () class AstropyFitsConverter(FitsConverter): tags = ("tag:astropy.org:astropy/fits/fits-*",) types = ("astropy.io.fits.hdu.hdulist.HDUList",) asdf-astropy-0.5.0/asdf_astropy/converters/fits/tests/000077500000000000000000000000001452515624200231355ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/fits/tests/__init__.py000066400000000000000000000000001452515624200252340ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/fits/tests/test_fits.py000066400000000000000000000060521452515624200255160ustar00rootroot00000000000000import asdf import numpy as np import pytest from asdf.testing.helpers import yaml_to_asdf from astropy.io import fits from numpy.testing import assert_array_equal from asdf_astropy.testing.helpers import assert_hdu_list_equal def create_hduls(): hdul = fits.HDUList() header = fits.Header([("FOO", "BAR", "BAZ"), ("SOMENUM", "11.0"), ("EMPTY",)]) hdul.append(fits.PrimaryHDU(header=header)) hdul.append(fits.ImageHDU(data=np.arange(100))) hdul_with_table = fits.HDUList() hdul_with_table.append(fits.BinTableHDU.from_columns(np.array([(0, 1), (2, 3)], dtype=[("A", int), ("B", int)]))) return [hdul, hdul_with_table] @pytest.mark.parametrize("hdul", create_hduls()) def test_serialization(hdul, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["hdul"] = hdul af.write_to(file_path) with asdf.open(file_path) as af: assert_hdu_list_equal(af["hdul"], hdul) def test_asdf_tag(): yaml = """ hdul: !fits/fits-1.0.0 - header: - [SIMPLE, true, conforms to FITS standard] - [BITPIX, 8, array data type] - [NAXIS, 0, number of array dimensions] - [EXTEND, true] - [] - ['', Top Level MIRI Metadata] - [] - [DATE, '2013-08-30T10:49:55.070373', The date this file was created (UTC)] - [FILENAME, MiriDarkReferenceModel_test.fits, The name of the file] - [TELESCOP, JWST, The telescope used to acquire the data] - [] - ['', Information about the observation] - [] - [DATE-OBS, '2013-08-30T10:49:55.000000', The date the observation was made (UTC)] - data: !core/ndarray-1.0.0 data: [2, 3, 3, 4] datatype: float32 shape: [4] header: - [XTENSION, IMAGE, Image extension] - [BITPIX, -32, array data type] - [NAXIS, 4, number of array dimensions] - [NAXIS1, 4] - [NAXIS2, 3] - [NAXIS3, 3] - [NAXIS4, 2] - [PCOUNT, 0, number of parameters] - [GCOUNT, 1, number of groups] - [EXTNAME, SCI, extension name] - [BUNIT, DN, Units of the data array] - data: !core/ndarray-1.0.0 data: [5, 6, 7, 8] datatype: int64 shape: [4] header: - [XTENSION, IMAGE, Image extension] - [BITPIX, -32, array data type] - [NAXIS, 4, number of array dimensions] - [NAXIS1, 4] - [NAXIS2, 3] - [NAXIS3, 3] - [NAXIS4, 2] - [PCOUNT, 0, number of parameters] - [GCOUNT, 1, number of groups] - [EXTNAME, ERR, extension name] - [BUNIT, DN, Units of the error array] """ buff = yaml_to_asdf(yaml) with asdf.open(buff) as af: hdul = af["hdul"] assert len(hdul) == 3 # noqa: PLR2004 assert hdul[0].header["FILENAME"] == "MiriDarkReferenceModel_test.fits" assert hdul[0].data is None assert hdul[1].header["EXTNAME"] == "SCI" assert_array_equal(hdul[1].data, np.array([2, 3, 3, 4], dtype=np.float32)) assert hdul[2].header["EXTNAME"] == "ERR" assert_array_equal(hdul[2].data, np.array([5, 6, 7, 8], dtype=np.int64)) asdf-astropy-0.5.0/asdf_astropy/converters/helpers.py000066400000000000000000000010401452515624200230350ustar00rootroot00000000000000from packaging.version import parse as parse_version def parse_tag_version(tag): """ Parse the version portion of the tag into a comparable version object. Parameters ---------- tag : str Returns ------- packaging.version.Version """ return parse_version(tag[tag.rfind("-") + 1 :]) def get_tag_name(tag): """ Extract the name portion of a tag URI. Parameters ---------- tag : str Returns ------- str """ return tag[tag.rfind("/") + 1 : tag.rfind("-")] asdf-astropy-0.5.0/asdf_astropy/converters/table/000077500000000000000000000000001452515624200221155ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/table/__init__.py000066400000000000000000000003371452515624200242310ustar00rootroot00000000000000__all__ = [ "ColumnConverter", "AstropyTableConverter", "AsdfTableConverter", "NdarrayMixinConverter", ] from .table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter asdf-astropy-0.5.0/asdf_astropy/converters/table/table.py000066400000000000000000000057561452515624200235730ustar00rootroot00000000000000from asdf.extension import Converter from asdf.tags.core.ndarray import NDArrayType class ColumnConverter(Converter): tags = ("tag:stsci.edu:asdf/core/column-*",) types = ( "astropy.table.column.Column", "astropy.table.column.MaskedColumn", ) def to_yaml_tree(self, obj, tag, ctx): node = {"data": obj.data, "name": obj.name} if obj.description: node["description"] = obj.description if obj.unit: node["unit"] = obj.unit if obj.meta: node["meta"] = obj.meta return node def from_yaml_tree(self, node, tag, ctx): from astropy.table import Column, MaskedColumn from numpy.ma.core import MaskedArray data = node["data"] if isinstance(data, NDArrayType): # TODO: Why doesn't NDArrayType work? This needs some research # and documentation. data = data._make_array() column_class = MaskedColumn if isinstance(data, MaskedArray) else Column return column_class( data=data, name=node["name"], description=node.get("description"), unit=node.get("unit"), meta=node.get("meta"), ) class AsdfTableConverter(Converter): tags = ("tag:stsci.edu:asdf/core/table-*",) types = () def to_yaml_tree(self, obj, tag, ctx): msg = "astropy does not support writing astropy.table.Table with the ASDF table-1.0.0 tag" raise NotImplementedError(msg) def from_yaml_tree(self, node, tag, ctx): from astropy.table import Table return Table(node["columns"], meta=node.get("meta")) class AstropyTableConverter(Converter): tags = ("tag:astropy.org:astropy/table/table-*",) types = ( "astropy.table.table.Table", "astropy.table.table.QTable", ) def to_yaml_tree(self, obj, tag, ctx): from astropy.table import QTable node = { "columns": [obj[name] for name in obj.colnames], "colnames": obj.colnames, "qtable": isinstance(obj, QTable), } if obj.meta: node["meta"] = obj.meta return node def from_yaml_tree(self, node, tag, ctx): from astropy.table import QTable, Table table = QTable(meta=node.get("meta")) if node.get("qtable", False) else Table(meta=node.get("meta")) for name, column in zip(node["colnames"], node["columns"]): table[name] = column return table class NdarrayMixinConverter(Converter): tags = ("tag:astropy.org:astropy/table/ndarraymixin-*",) types = ("astropy.table.ndarray_mixin.NdarrayMixin",) def to_yaml_tree(self, obj, tag, ctx): import numpy as np return {"array": np.asarray(obj)} def from_yaml_tree(self, node, tag, ctx): from astropy.table import NdarrayMixin arr = node["array"] # this will trigger reading the ASDF block that contains the array data return arr.view(NdarrayMixin) asdf-astropy-0.5.0/asdf_astropy/converters/table/tests/000077500000000000000000000000001452515624200232575ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/table/tests/__init__.py000066400000000000000000000000001452515624200253560ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/table/tests/test_table.py000066400000000000000000000204741452515624200257660ustar00rootroot00000000000000import warnings import asdf import astropy.units as u import numpy as np import pytest from asdf.testing.helpers import yaml_to_asdf from astropy.coordinates import EarthLocation, SkyCoord from astropy.table import NdarrayMixin, QTable, Table from astropy.time import Time, TimeDelta from numpy.testing import assert_array_equal from asdf_astropy.testing import helpers def assert_description_equal(a, b): message = ( "asdf_astropy.converters.table.tests.test_table.assert_description_equal is deprecated." "Use asdf_astropy.testing.helpers.assert_description_equal instead." ) warnings.warn(message, DeprecationWarning, stacklevel=2) return helpers.assert_description_equal(a, b) def assert_table_equal(a, b): message = ( "asdf_astropy.converters.table.tests.test_table.assert_table_equal is deprecated." "Use asdf_astropy.testing.helpers.assert_table_equal instead." ) warnings.warn(message, DeprecationWarning, stacklevel=2) return helpers.assert_table_equal(a, b) def assert_table_roundtrip(table, tmp_path): message = ( "asdf_astropy.converters.table.tests.test_table.assert_table_roundtrip is deprecated." "Use asdf_astropy.testing.helpers.assert_table_roundtrip instead." ) warnings.warn(message, DeprecationWarning, stacklevel=2) return helpers.assert_table_roundtrip(table, tmp_path) def test_deprecations(tmp_path): rows = [(1, 2.0, "x"), (4, 5.0, "y"), (5, 8.2, "z")] table = Table(rows=rows, names=("a", "b", "c"), dtype=("i4", "f8", "S1")) table.columns["a"].description = "RA" table.columns["a"].unit = "degree" table.columns["a"].meta = {"foo": "bar"} table.columns["c"].description = "Some description of some sort" # Test assert_description_equal deprecation with pytest.warns(DeprecationWarning, match=".*assert_description_equal.*"): assert_description_equal("a", "a") # Test assert_table_equal deprecation with pytest.warns(DeprecationWarning, match=".*assert_table_equal.*"): assert_table_equal(table, table) # Test assert_table_roundtrip deprecation with pytest.warns(DeprecationWarning, match=".*assert_table_roundtrip.*"): assert_table_roundtrip(table, tmp_path) def test_table(tmp_path): rows = [(1, 2.0, "x"), (4, 5.0, "y"), (5, 8.2, "z")] table = Table(rows=rows, names=("a", "b", "c"), dtype=("i4", "f8", "S1")) table.columns["a"].description = "RA" table.columns["a"].unit = "degree" table.columns["a"].meta = {"foo": "bar"} table.columns["c"].description = "Some description of some sort" helpers.assert_table_roundtrip(table, tmp_path) def test_array_columns(tmp_path): data = np.array( [([[1, 2], [3, 4]], 2.0, "x"), ([[5, 6], [7, 8]], 5.0, "y"), ([[9, 10], [11, 12]], 8.2, "z")], dtype=[("a", " columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 data: [0, 1, 2] name: a - !core/column-1.0.0 data: !core/ndarray-1.0.0 data: [0, 1, 2, 3] name: b colnames: [a, b] """ buff = yaml_to_asdf(yaml) with pytest.raises(ValueError, match="Inconsistent data column lengths"), asdf.open(buff): pass def test_masked_table(tmp_path): rows = [(1, 2.0, "x"), (4, 5.0, "y"), (5, 8.2, "z")] table = Table(rows=rows, names=("a", "b", "c"), dtype=("i4", "f8", "S1"), masked=True) table.columns["a"].description = "RA" table.columns["a"].unit = "degree" table.columns["a"].meta = {"foo": "bar"} table.columns["a"].mask = [True, False, True] table.columns["c"].description = "Some description of some sort" helpers.assert_table_roundtrip(table, tmp_path) def test_quantity_mixin(tmp_path): table = QTable() table["a"] = [1, 2, 3] table["b"] = ["x", "y", "z"] table["c"] = [2.0, 5.0, 8.2] * u.m helpers.assert_table_roundtrip(table, tmp_path) def test_time_mixin(tmp_path): table = Table() table["a"] = [1, 2] table["b"] = ["x", "y"] table["c"] = Time(["2001-01-02T12:34:56", "2001-02-03T00:01:02"]) result = helpers.assert_table_roundtrip(table, tmp_path) helpers.assert_time_equal(result["c"], table["c"]) def test_timedelta_mixin(tmp_path): table = Table() table["a"] = [1, 2] table["b"] = ["x", "y"] table["c"] = TimeDelta([1, 2] * u.day) result = helpers.assert_table_roundtrip(table, tmp_path) helpers.assert_time_delta_equal(result["c"], table["c"]) def test_skycoord_mixin(tmp_path): table = Table() table["a"] = [1, 2] table["b"] = ["x", "y"] table["c"] = SkyCoord([1, 2], [3, 4], unit="deg,deg", frame="fk4", obstime="J1990.5") # We can't use our assert_table_roundtrip helper because # astropy 4.0.x does not include an __eq__ method for SKyCoord. file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["table"] = table af.write_to(file_path) with asdf.open(file_path) as af: helpers.assert_sky_coord_equal(af["table"]["c"], table["c"]) def test_earthlocation_mixin(tmp_path): table = Table() table["a"] = [1, 2] table["b"] = ["x", "y"] table["c"] = EarthLocation(x=[1, 2] * u.km, y=[3, 4] * u.km, z=[5, 6] * u.km) result = helpers.assert_table_roundtrip(table, tmp_path) helpers.assert_earth_location_equal(result["c"], table["c"]) def test_ndarray_mixin(tmp_path): table = Table() table["a"] = [1, 2] table["b"] = ["x", "y"] table["c"] = NdarrayMixin([5, 6]) result = helpers.assert_table_roundtrip(table, tmp_path) assert isinstance(result["c"], NdarrayMixin) def test_asdf_table(): yaml = """ table: !core/table-1.0.0 columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 data: [1, 2, 3] datatype: float64 shape: [3] description: RA meta: {foo: bar} name: a unit: !unit/unit-1.0.0 deg - !core/column-1.0.0 data: !core/ndarray-1.0.0 data: [4, 5, 6] datatype: float64 shape: [3] description: DEC name: b - !core/column-1.0.0 data: !core/ndarray-1.0.0 data: [d, e, f] datatype: [ascii, 1] shape: [3] description: The target name name: c """ buff = yaml_to_asdf(yaml) with asdf.open(buff) as af: table = af["table"] assert isinstance(table, Table) assert table["a"].name == "a" assert table["a"].description == "RA" assert table["a"].meta == {"foo": "bar"} assert table["a"].unit == u.deg assert_array_equal(table["a"].data, np.array([1, 2, 3], dtype=np.float64)) assert table["b"].name == "b" assert table["b"].description == "DEC" assert_array_equal(table["b"].data, np.array([4, 5, 6], dtype=np.float64)) assert table["c"].name == "c" assert table["c"].description == "The target name" assert_array_equal(table["c"].data, np.array([b"d", b"e", b"f"])) asdf-astropy-0.5.0/asdf_astropy/converters/time/000077500000000000000000000000001452515624200217645ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/time/__init__.py000066400000000000000000000002111452515624200240670ustar00rootroot00000000000000__all__ = [ "TimeDeltaConverter", "TimeConverter", ] from .time import TimeConverter from .time_delta import TimeDeltaConverter asdf-astropy-0.5.0/asdf_astropy/converters/time/tests/000077500000000000000000000000001452515624200231265ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/time/tests/__init__.py000066400000000000000000000000001452515624200252250ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/time/tests/test_time.py000066400000000000000000000137401452515624200255020ustar00rootroot00000000000000import unittest.mock as mk from datetime import datetime import asdf import numpy as np import pytest from asdf.testing.helpers import yaml_to_asdf from astropy import units as u from astropy.coordinates import EarthLocation from astropy.time import Time from asdf_astropy.testing.helpers import assert_time_equal def create_times(): return [ Time(1950.0, format="byear"), Time("B1950.0", format="byear_str"), Time( [1, 2], location=EarthLocation(x=[1, 2] * u.m, y=[3, 4] * u.m, z=[5, 6] * u.m), format="cxcsec", ), Time(datetime(2000, 1, 2, 12, 0, 0), format="datetime"), Time(2000.45, format="decimalyear"), Time("2000-01-01T00:00:00.000", format="fits"), Time(630720013.0, format="gps"), Time("2000-01-01 00:00:00.000", format="iso"), Time("2000-01-01T00:00:00.000", format="isot"), Time(2451544.5, format="jd"), Time(2000.0, format="jyear"), Time( "J2000.000", location=EarthLocation(x=6378100 * u.m, y=0 * u.m, z=0 * u.m), format="jyear_str", ), Time(51544.0, format="mjd"), Time(Time("2000-01-01T00:00:00.000").plot_date, format="plot_date"), # astropy/astropy#14080 Time(np.arange(100), format="unix"), Time(946684800.0, format="unix_tai"), Time("2000:001:00:00:00.000", format="yday"), Time("2000-01-01T00:00:00.000"), Time({"year": 2010, "month": 3, "day": 1}, format="ymdhms"), Time(np.datetime64("2000-01-01T01:01:01"), format="datetime64"), Time(["2001-01-02T12:34:56", "2001-02-03T00:01:02"]), ] @pytest.mark.parametrize("time", create_times()) @pytest.mark.parametrize("version", asdf.versioning.supported_versions) def test_serialization(time, version, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile(version=version) as af: af["time"] = time af.write_to(file_path) with asdf.open(file_path) as af: assert_time_equal(af["time"], time) def create_examples(): return [ {"example": '!time/time-1.1.0 "2000-12-31T13:05:27.737"', "truth": Time("2000-12-31T13:05:27.737")}, {"example": '!time/time-1.1.0 "2001:003:04:05:06.789"', "truth": Time("2001:003:04:05:06.789")}, {"example": "!time/time-1.1.0 B2000.0", "truth": Time("B2000.0")}, { "example": """!time/time-1.1.0 value: 2000.0 format: byear""", "truth": Time(2000.0, format="byear"), }, { "example": """!time/time-1.1.0 ["2000-12-31T13:05:27.737", "2000-12-31T13:06:38.444"]""", "truth": Time(["2000-12-31T13:05:27.737", "2000-12-31T13:06:38.444"]), }, { "example": """!time/time-1.1.0 value: !core/ndarray-1.0.0 data: [2000, 2001] datatype: float64 format: jyear""", "truth": Time([2000, 2001], format="jyear"), }, { "example": """!time/time-1.1.0 value: !core/ndarray-1.0.0 value: 2000.0 format: jyear scale: tdb location: x: !unit/quantity-1.1.0 value: 6378100 unit: !unit/unit-1.0.0 m y: !unit/quantity-1.1.0 value: 0 unit: !unit/unit-1.0.0 m z: !unit/quantity-1.1.0 value: 0 unit: !unit/unit-1.0.0 m format: jyear""", "truth": Time( 2000.0, location=EarthLocation(x=6378100 * u.m, y=0 * u.m, z=0 * u.m), scale="tdb", format="jyear", ), }, ] @pytest.mark.parametrize("example", create_examples()) def test_read_examples(example): buff = yaml_to_asdf(f"example: {example['example'].strip()}") with asdf.AsdfFile() as af: af._open_impl(af, buff, mode="rw") assert np.all(af["example"] == example["truth"]) def test_error(): from asdf_astropy.converters.time.time import TimeConverter with mk.patch("asdf_astropy.converters.time.time._ASTROPY_FORMAT_TO_ASDF_FORMAT") as mock_time: mock_time.get.return_value = "Bad time" example = "2000-01-01 00:00:00.000" with pytest.raises(ValueError, match=f"ASDF time '{example}' is not one of the recognized implicit formats"): TimeConverter().from_yaml_tree(example, mk.MagicMock(), mk.MagicMock()) def create_formats(): from astropy.time.formats import TIME_FORMATS formats = [] for format_ in TIME_FORMATS: new = Time("B2000.0") new.format = format_ formats.append(new) return formats @pytest.mark.parametrize("time", create_formats()) def test_formats(time, tmp_path): atol = 1 * u.day if "jyear" in time.format else 1e-08 * u.day file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["time"] = time af.write_to(file_path) with asdf.open(file_path) as af: assert af["time"].isclose(time, atol=atol) assert af["time"].format == time.format def create_str_formats(): return [ Time("2000-01-01 00:00:00.000", format="iso"), Time("B2000.0", scale="utc", format="byear_str"), Time("J2000.000", scale="utc", format="jyear_str"), Time("2000:001:00:00:00.000", format="yday"), ] @pytest.mark.parametrize("time", create_str_formats()) def test_str_format(time, tmp_path): """Test times that should serialize as strings""" file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["time"] = time af.write_to(file_path) with asdf.open(file_path, _force_raw_types=True) as af: assert isinstance(af["time"], str) assert str(af["time"]) == time.value atol = 1 * u.day if "jyear" in time.format or "byear" in time.format else 1e-08 * u.day with asdf.open(file_path) as af: assert af["time"].isclose(time, atol=atol) assert af["time"].format == time.format asdf-astropy-0.5.0/asdf_astropy/converters/time/tests/test_time_delta.py000066400000000000000000000014401452515624200266450ustar00rootroot00000000000000import asdf import pytest from astropy import units as u from astropy.time import Time, TimeDelta from asdf_astropy.testing.helpers import assert_time_delta_equal def create_time_deltas(): result = [TimeDelta([1, 2] * u.day)] result += [TimeDelta(Time.now() - Time.now(), format=format_) for format_ in TimeDelta.FORMATS] result += [TimeDelta(0.125 * u.day, scale=scale) for scale in [*list(TimeDelta.SCALES), None]] return result @pytest.mark.parametrize("time_delta", create_time_deltas()) def test_serialization(time_delta, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["time_delta"] = time_delta af.write_to(file_path) with asdf.open(file_path) as af: assert_time_delta_equal(af["time_delta"], time_delta) asdf-astropy-0.5.0/asdf_astropy/converters/time/time.py000066400000000000000000000075351452515624200233060ustar00rootroot00000000000000import numpy as np from asdf.extension import Converter from asdf.tags.core.ndarray import NDArrayType _GUESSABLE_FORMATS = {"iso", "byear", "jyear", "yday"} _ASTROPY_FORMAT_TO_ASDF_FORMAT = { "isot": "iso", "byear_str": "byear", "jyear_str": "jyear", } class TimeConverter(Converter): tags = ("tag:stsci.edu:asdf/time/time-*",) types = ("astropy.time.core.Time",) def to_yaml_tree(self, obj, tag, ctx): from astropy.time import Time base_format = obj.format if base_format == "byear": obj = Time(obj, format="byear_str") elif base_format == "jyear": obj = Time(obj, format="jyear_str") elif base_format in ("fits", "datetime", "plot_date", "ymdhms", "datetime64"): obj = Time(obj, format="isot") asdf_format = _ASTROPY_FORMAT_TO_ASDF_FORMAT.get(obj.format, obj.format) guessable_format = asdf_format in _GUESSABLE_FORMATS if obj.scale == "utc" and guessable_format and obj.isscalar and base_format == obj.format: return obj.value node = { "value": obj.value, } if not guessable_format: node["format"] = asdf_format if base_format != obj.format: node["base_format"] = base_format if obj.scale != "utc": node["scale"] = obj.scale if obj.location is not None: # The 1.0.0 and 1.1.0 tags differ in how location is represented. # In 1.0.0, there is a single "unit" property that is shared among # x, y, and z, and in 1.1.0 each is a quantity with its own unit. location = ( { "x": obj.location.x.value, "y": obj.location.y.value, "z": obj.location.z.value, "unit": obj.location.unit, } if tag.endswith("1.0.0") else { "x": obj.location.x, "y": obj.location.y, "z": obj.location.z, } ) node["location"] = location return node def from_yaml_tree(self, node, tag, ctx): from astropy import units from astropy.coordinates import EarthLocation from astropy.time import Time if isinstance(node, (str, list, np.ndarray, NDArrayType)): time = Time(node) asdf_format = _ASTROPY_FORMAT_TO_ASDF_FORMAT.get(time.format, time.format) if asdf_format not in _GUESSABLE_FORMATS: msg = f"ASDF time '{node}' is not one of the recognized implicit formats" raise ValueError(msg) return time location = node.get("location") if location is not None: # The 1.0.0 and 1.1.0 tags differ in how location is represented. # In 1.0.0, there is a single "unit" property that is shared among # x, y, and z, and in 1.1.0 each is a quantity with its own unit. if tag.endswith("1.0.0"): unit = location.get("unit", units.m) location = EarthLocation.from_geocentric( units.Quantity(location["x"], unit=unit), units.Quantity(location["y"], unit=unit), units.Quantity(location["z"], unit=unit), ) else: location = EarthLocation.from_geocentric( location["x"], location["y"], location["z"], ) time = Time( node["value"], format=node.get("format"), scale=node.get("scale"), location=location, ) base_format = node.get("base_format") if base_format is not None and base_format != time.format: time.format = base_format return time asdf-astropy-0.5.0/asdf_astropy/converters/time/time_delta.py000066400000000000000000000006441452515624200244510ustar00rootroot00000000000000from asdf.extension import Converter class TimeDeltaConverter(Converter): tags = ("tag:astropy.org:astropy/time/timedelta-*",) types = ("astropy.time.core.TimeDelta",) def to_yaml_tree(self, obj, tag, ctx): return obj.info._represent_as_dict() def from_yaml_tree(self, node, tag, ctx): from astropy.time.core import TimeDelta return TimeDelta.info._construct_from_dict(node) asdf-astropy-0.5.0/asdf_astropy/converters/transform/000077500000000000000000000000001452515624200230415ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/transform/__init__.py000066400000000000000000000021441452515624200251530ustar00rootroot00000000000000__all__ = [ "CompoundConverter", "TransformConverterBase", "SimpleTransformConverter", "ConstantConverter", "IdentityConverter", "RemapAxesConverter", "UnitsMappingConverter", "MathFunctionsConverter", "PolynomialConverter", "OrthoPolynomialConverter", "ProjectionConverter", "ModelBoundingBoxConverter", "CompoundBoundingBoxConverter", "Rotate3DConverter", "RotationSequenceConverter", "SplineConverter", "TabularConverter", ] from .compound import CompoundConverter from .core import SimpleTransformConverter, TransformConverterBase from .functional_models import ConstantConverter from .mappings import IdentityConverter, RemapAxesConverter, UnitsMappingConverter from .math_functions import MathFunctionsConverter from .polynomial import OrthoPolynomialConverter, PolynomialConverter from .projections import ProjectionConverter from .properties import CompoundBoundingBoxConverter, ModelBoundingBoxConverter from .rotations import Rotate3DConverter, RotationSequenceConverter from .spline import SplineConverter from .tabular import TabularConverter asdf-astropy-0.5.0/asdf_astropy/converters/transform/compound.py000066400000000000000000000053001452515624200252350ustar00rootroot00000000000000from asdf_astropy.converters.helpers import get_tag_name from .core import TransformConverterBase __all__ = ["CompoundConverter"] _OPERATOR_TO_TAG_NAME = { "+": "add", "-": "subtract", "*": "multiply", "/": "divide", "**": "power", "|": "compose", "&": "concatenate", "fix_inputs": "fix_inputs", } _TAG_NAME_TO_MODEL_METHOD = { "add": "__add__", "subtract": "__sub__", "multiply": "__mul__", "divide": "__truediv__", "power": "__pow__", "compose": "__or__", "concatenate": "__and__", "fix_inputs": "fix_inputs", } class CompoundConverter(TransformConverterBase): """ ASDF serialization support for CompoundModel. """ tags = ( "tag:stsci.edu:asdf/transform/add-*", "tag:stsci.edu:asdf/transform/subtract-*", "tag:stsci.edu:asdf/transform/multiply-*", "tag:stsci.edu:asdf/transform/divide-*", "tag:stsci.edu:asdf/transform/power-*", "tag:stsci.edu:asdf/transform/compose-*", "tag:stsci.edu:asdf/transform/concatenate-*", "tag:stsci.edu:asdf/transform/fix_inputs-*", ) types = ("astropy.modeling.core.CompoundModel",) def select_tag(self, model, tags, ctx): tag_name = _OPERATOR_TO_TAG_NAME[model.op] # The extension will never include two tags with the # same name but different version, so we can just # return the first matching tag that we discover in # the list: return next(t for t in tags if get_tag_name(t) == tag_name) def to_yaml_tree_transform(self, model, tag, ctx): left = model.left right = ( { "keys": list(model.right.keys()), "values": list(model.right.values()), } if isinstance(model.right, dict) else model.right ) return {"forward": [left, right]} def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.core import CompoundModel, Model oper = _TAG_NAME_TO_MODEL_METHOD[get_tag_name(tag)] left = node["forward"][0] if not isinstance(left, Model): msg = f"Unknown left model type '{node['forward'][0]._tag}'" raise TypeError(msg) right = node["forward"][1] if (oper == "fix_inputs" and not isinstance(right, dict)) or ( oper != "fix_inputs" and not isinstance(right, Model) ): msg = f"Unknown right model type '{node['forward'][1]._tag}'" raise TypeError(msg) if oper == "fix_inputs": right = dict(zip(right["keys"], right["values"])) return CompoundModel("fix_inputs", left, right) return getattr(left, oper)(right) asdf-astropy-0.5.0/asdf_astropy/converters/transform/core.py000066400000000000000000000204071452515624200243460ustar00rootroot00000000000000import abc from asdf.extension import Converter from asdf_astropy.converters.utils import import_type def parameter_to_value(param): """ Convert a model parameter to a Quantity or number, depending on the presence of a unit. Parameters ---------- param : astropy.modeling.Parameter Returns ------- astropy.units.Quantity or float """ from astropy import units as u if param.unit is not None: return u.Quantity(param) return param.value # One converter, UnitsMappingConverter, does not inherit # this class. When adding features here consider also # updating UnitsMappingConverter. # This class is used by other packages, e.g., gwcs, to implement # converters for custom models. Keep that in mind when modifying # this code. class TransformConverterBase(Converter): """ ABC for transform/model converters. Handles common properties after concrete converter sets model-specific properties. """ @abc.abstractmethod def to_yaml_tree_transform(self, model, tag, ctx): """ Convert a model's parameters into a dict suitable for ASDF serialization. Common model properties such as name and inverse will be handled by this base class. Parameters ---------- model : astropy.modeling.Model The model instance to convert. tag : str The tag identifying the YAML type that `astropy.modeling.Model` should be converted into. ctx : asdf.asdf.SerializationContext The context of the current serialization request. Returns ------- dict ASDF node. """ @abc.abstractmethod def from_yaml_tree_transform(self, node, tag, ctx): """ Convert an ASDF node into an instance of the appropriate model class. The implementing class need only instantiate the model and set parameter values; common model properties such as name and inverse will be handled by this base class. Parameters ---------- node : dict The ASDF node to convert. tag : str The tag identifying the YAML type of the node. ctx : asdf.asdf.SerializationContext The context of the current serialization request. Returns ------- astropy.modeling.Model The resulting model instance. """ def to_yaml_tree(self, model, tag, ctx): from astropy.modeling.core import CompoundModel node = self.to_yaml_tree_transform(model, tag, ctx) if model.name is not None: node["name"] = model.name node["inputs"] = list(model.inputs) node["outputs"] = list(model.outputs) # Don't bother serializing analytic inverses provided # by the model: if getattr(model, "_user_inverse", None) is not None: node["inverse"] = model._user_inverse self._serialize_bounding_box(model, node) # model / parameter constraints if not isinstance(model, CompoundModel): fixed_nondefaults = {k: f for k, f in model.fixed.items() if f} if fixed_nondefaults: node["fixed"] = fixed_nondefaults bounds_nondefaults = {k: b for k, b in model.bounds.items() if any(b)} if bounds_nondefaults: node["bounds"] = bounds_nondefaults # model input_units_equivalencies if not isinstance(model, CompoundModel) and model.input_units_equivalencies: node["input_units_equivalencies"] = model.input_units_equivalencies return node def _serialize_bounding_box(self, model, node): from astropy.modeling.bounding_box import CompoundBoundingBox, ModelBoundingBox # ignore any default bounding_box if (bbox := model._user_bounding_box) is not None: if isinstance(bbox, ModelBoundingBox): self._serialize_bbox(model, node) elif isinstance(bbox, CompoundBoundingBox): self._serialize_cbbox(model, node) def _serialize_bbox(self, model, node): from astropy.modeling.bounding_box import ModelBoundingBox from astropy.utils import minversion bbox = model.bounding_box if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): if len(bbox.ignored) > 0: if minversion("astropy", "5.1"): kwargs = {"_preserve_ignore": True} else: msg = "Bounding box ignored arguments are only supported by astropy 5.1+" raise RuntimeError(msg) else: kwargs = {} bbox = ModelBoundingBox.validate(model, bbox, **kwargs) else: if len(bbox.ignored) > 0: msg = "asdf-transform-schemas > 0.2.2 in order to serialize a bounding_box with ignored" raise RuntimeError(msg) bbox = bbox.bounding_box(order="C") bbox = list(bbox) if model.n_inputs == 1 else [list(item) for item in bbox] node["bounding_box"] = bbox def _serialize_cbbox(self, model, node): from astropy.utils import minversion bbox = model.bounding_box if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): node["bounding_box"] = bbox else: msg = "asdf-transform-schemas > 0.2.2 in order to serialize a compound bounding_box" raise RuntimeError(msg) def from_yaml_tree(self, node, tag, ctx): from astropy.modeling.core import CompoundModel model = self.from_yaml_tree_transform(node, tag, ctx) if "name" in node: model.name = node["name"] if "inputs" in node: model.inputs = tuple(node["inputs"]) if "outputs" in node: model.outputs = tuple(node["outputs"]) self._deserialize_bounding_box(model, node) param_and_model_constraints = {} for constraint in ["fixed", "bounds"]: if constraint in node: param_and_model_constraints[constraint] = node[constraint] model._initialize_constraints(param_and_model_constraints) # this still writes eqs. for compound, but operates on each sub model if "input_units_equivalencies" in node and not isinstance(model, CompoundModel): model.input_units_equivalencies = node["input_units_equivalencies"] yield model if "inverse" in node: model.inverse = node["inverse"] def _deserialize_bounding_box(self, model, node): if "bounding_box" in node: bounding_box = node["bounding_box"] if isinstance(bounding_box, list): model.bounding_box = bounding_box elif callable(bounding_box): model.bounding_box = bounding_box(model) else: msg = f"Cannot form bounding_box from: {bounding_box}" raise TypeError(msg) class SimpleTransformConverter(TransformConverterBase): """ Class for converters that serialize all of a model's parameters and do not require special behavior based on tag version. Parameters ---------- tags : list of str Tag patterns. model_type_name Fully-qualified model type name. """ def __init__(self, tags, model_type_name): self._tags = tags self._model_type_name = model_type_name self._model_type = None @property def tags(self): return self._tags @property def types(self): return [self._model_type_name] @property def model_type(self): # Delay import until the model class is needed to improve speed # of loading the extension. if self._model_type is None: self._model_type = import_type(self._model_type_name) return self._model_type def to_yaml_tree_transform(self, model, tag, ctx): return {p: parameter_to_value(getattr(model, p)) for p in model.param_names} def from_yaml_tree_transform(self, node, tag, ctx): model_type = self.model_type model_kwargs = {} for param in model_type.param_names: if param in node: model_kwargs[param] = node[param] return model_type(**model_kwargs) asdf-astropy-0.5.0/asdf_astropy/converters/transform/functional_models.py000066400000000000000000000033371452515624200271260ustar00rootroot00000000000000from packaging.version import parse as parse_version from asdf_astropy.converters.helpers import parse_tag_version from .core import TransformConverterBase, parameter_to_value class ConstantConverter(TransformConverterBase): """ ASDF support for serializing the Const1D and Const2D models. """ # The 'dimensions' property was added in 1.4.0, # previously all values were 1D. _2D_MIN_VERSION = parse_version("1.4.0") tags = ("tag:stsci.edu:asdf/transform/constant-*",) types = ( "astropy.modeling.functional_models.Const1D", "astropy.modeling.functional_models.Const2D", ) def to_yaml_tree_transform(self, model, tag, ctx): from astropy.modeling.functional_models import Const1D, Const2D if parse_tag_version(tag) < self._2D_MIN_VERSION: if not isinstance(model, Const1D): msg = f"{tag} does not support models with > 1 dimension" raise TypeError(msg) return {"value": parameter_to_value(model.amplitude)} if isinstance(model, Const1D): dimension = 1 elif isinstance(model, Const2D): dimension = 2 return {"value": parameter_to_value(model.amplitude), "dimensions": dimension} def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.functional_models import Const1D, Const2D if parse_tag_version(tag) < self._2D_MIN_VERSION: return Const1D(node["value"]) if node["dimensions"] == 1: return Const1D(node["value"]) if node["dimensions"] == 2: # noqa: PLR2004 return Const2D(node["value"]) msg = f"Invalid dimensions: {node['dimensions']}" raise RuntimeError(msg) asdf-astropy-0.5.0/asdf_astropy/converters/transform/mappings.py000066400000000000000000000065641452515624200252440ustar00rootroot00000000000000from asdf.extension import Converter from .core import TransformConverterBase class IdentityConverter(TransformConverterBase): """ ASDF support for serializing the Identity model. """ tags = ("tag:stsci.edu:asdf/transform/identity-*",) types = ("astropy.modeling.mappings.Identity",) def to_yaml_tree_transform(self, model, tag, ctx): node = {} if model.n_inputs != 1: node["n_dims"] = model.n_inputs return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.mappings import Identity return Identity(node.get("n_dims", 1)) class RemapAxesConverter(TransformConverterBase): """ ASDF support for serializing the Mapping model """ tags = ("tag:stsci.edu:asdf/transform/remap_axes-*",) types = ("astropy.modeling.mappings.Mapping",) def to_yaml_tree_transform(self, model, tag, ctx): node = {"mapping": list(model.mapping)} if model.n_inputs > max(model.mapping) + 1: node["n_inputs"] = model.n_inputs return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.models import Mapping return Mapping(tuple(node["mapping"]), node.get("n_inputs")) class UnitsMappingConverter(Converter): """ ASDF support for serializing the UnitsMapping model. Note that this converter does not inherit from TransformConverterBase, because the inputs and outputs are written differently from other models. """ tags = ("tag:astropy.org:astropy/transform/units_mapping-*",) types = ("astropy.modeling.mappings.UnitsMapping",) def to_yaml_tree(self, model, tag, ctx): node = {} if model.name is not None: node["name"] = model.name inputs = [] outputs = [] for i, o, m in zip(model.inputs, model.outputs, model.mapping): input_ = { "name": i, "allow_dimensionless": model.input_units_allow_dimensionless[i], } if m[0] is not None: input_["unit"] = m[0] if model.input_units_equivalencies is not None and i in model.input_units_equivalencies: input_["equivalencies"] = model.input_units_equivalencies[i] inputs.append(input_) output = { "name": o, } if m[-1] is not None: output["unit"] = m[-1] outputs.append(output) node["unit_inputs"] = inputs node["unit_outputs"] = outputs return node def from_yaml_tree(self, node, tag, ctx): from astropy.modeling.mappings import UnitsMapping mapping = tuple((i.get("unit"), o.get("unit")) for i, o in zip(node["unit_inputs"], node["unit_outputs"])) equivalencies = None for i in node["unit_inputs"]: if "equivalencies" in i: if equivalencies is None: equivalencies = {} equivalencies[i["name"]] = i["equivalencies"] kwargs = { "input_units_equivalencies": equivalencies, "input_units_allow_dimensionless": { i["name"]: i.get("allow_dimensionless", False) for i in node["unit_inputs"] }, } if "name" in node: kwargs["name"] = node["name"] return UnitsMapping(mapping, **kwargs) asdf-astropy-0.5.0/asdf_astropy/converters/transform/math_functions.py000066400000000000000000000031731452515624200264400ustar00rootroot00000000000000from .core import TransformConverterBase _MODEL_NAMES = [ "AbsoluteUfunc", "AddUfunc", "ArccosUfunc", "ArccoshUfunc", "ArcsinUfunc", "ArcsinhUfunc", "Arctan2Ufunc", "ArctanUfunc", "ArctanhUfunc", "CbrtUfunc", "CosUfunc", "CoshUfunc", "Deg2radUfunc", # DivideUfunc is an alias for True_divideUfunc "DivmodUfunc", "Exp2Ufunc", "ExpUfunc", "Expm1Ufunc", "FabsUfunc", "Floor_divideUfunc", "FmodUfunc", "HypotUfunc", "Log10Ufunc", "Log1pUfunc", "Log2Ufunc", "LogUfunc", "Logaddexp2Ufunc", "LogaddexpUfunc", # ModUfunc is an alias for RemainderUfunc "MultiplyUfunc", "NegativeUfunc", "PositiveUfunc", "PowerUfunc", "Rad2degUfunc", "ReciprocalUfunc", "RemainderUfunc", "RintUfunc", "SinUfunc", "SinhUfunc", "SqrtUfunc", "SquareUfunc", "SubtractUfunc", "TanUfunc", "TanhUfunc", "True_divideUfunc", ] class MathFunctionsConverter(TransformConverterBase): """ ASDF support for serializing the math functions models, each of which corresponds to a numpy ufunc. """ tags = ("tag:stsci.edu:asdf/transform/math_functions-*",) types = tuple("astropy.modeling.math_functions." + m for m in _MODEL_NAMES) def to_yaml_tree_transform(self, model, tag, ctx): return {"func_name": model.func.__name__} def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling import math_functions klass_name = math_functions._make_class_name(node["func_name"]) klass = getattr(math_functions, klass_name) return klass() asdf-astropy-0.5.0/asdf_astropy/converters/transform/polynomial.py000066400000000000000000000157441452515624200256110ustar00rootroot00000000000000import numpy as np from packaging.version import parse as parse_version from asdf_astropy.converters.helpers import parse_tag_version from .core import TransformConverterBase class PolynomialConverter(TransformConverterBase): """ ASDF support for serializing the 1D and 2D polynomial models. """ # Schema versions prior to 1.2 included an unrelated "domain" # property. We can't serialize the new domain values with those # versions because they don't validate. _DOMAIN_WINDOW_MIN_VERSION = parse_version("1.2.0") tags = ("tag:stsci.edu:asdf/transform/polynomial-*",) types = ( "astropy.modeling.polynomial.Polynomial1D", "astropy.modeling.polynomial.Polynomial2D", ) def to_yaml_tree_transform(self, model, tag, ctx): from astropy.modeling.polynomial import Polynomial1D, Polynomial2D if isinstance(model, Polynomial1D): coefficients = np.array(model.parameters) elif isinstance(model, Polynomial2D): degree = model.degree coefficients = np.zeros((degree + 1, degree + 1)) for i in range(degree + 1): for j in range(degree + 1): if i + j < degree + 1: name = "c" + str(i) + "_" + str(j) coefficients[i, j] = getattr(model, name).value node = {"coefficients": coefficients} if parse_tag_version(tag) >= self._DOMAIN_WINDOW_MIN_VERSION: if model.n_inputs == 1: if model.domain is not None: node["domain"] = model.domain if model.window is not None: node["window"] = model.window else: if model.x_domain or model.y_domain is not None: node["domain"] = (model.x_domain, model.y_domain) if model.x_window or model.y_window is not None: node["window"] = (model.x_window, model.y_window) return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.polynomial import Polynomial1D, Polynomial2D coefficients = np.asarray(node["coefficients"]) n_dim = coefficients.ndim if n_dim == 1: domain = node.get("domain", None) window = node.get("window", None) model = Polynomial1D(coefficients.size - 1, domain=domain, window=window) model.parameters = coefficients elif n_dim == 2: # noqa: PLR2004 x_domain, y_domain = tuple(node.get("domain", (None, None))) x_window, y_window = tuple(node.get("window", (None, None))) shape = coefficients.shape degree = shape[0] - 1 if shape[0] != shape[1]: msg = "Coefficients must be an (n+1, n+1) matrix" raise TypeError(msg) coeffs = {} for i in range(shape[0]): for j in range(shape[0]): if i + j < degree + 1: name = "c" + str(i) + "_" + str(j) coeffs[name] = coefficients[i, j] model = Polynomial2D( degree, x_domain=x_domain, y_domain=y_domain, x_window=x_window, y_window=y_window, **coeffs, ) else: msg = "astropy supports only 1D or 2D polynomial models" raise NotImplementedError(msg) return model _CLASS_NAME_TO_POLY_INFO = { "Legendre1D": ("legendre", 1), "Legendre2D": ("legendre", 2), "Chebyshev1D": ("chebyshev", 1), "Chebyshev2D": ("chebyshev", 2), "Hermite1D": ("hermite", 1), "Hermite2D": ("hermite", 2), } _POLY_INFO_TO_CLASS_NAME = {v: k for k, v in _CLASS_NAME_TO_POLY_INFO.items()} class OrthoPolynomialConverter(TransformConverterBase): """ ASDF support for serializing models that inherit OrthoPolyomialBase. """ # Map of model class name to (polynomial type, number of dimensions) tuple: tags = ("tag:stsci.edu:asdf/transform/ortho_polynomial-*",) types = ( "astropy.modeling.polynomial.Legendre1D", "astropy.modeling.polynomial.Legendre2D", "astropy.modeling.polynomial.Chebyshev1D", "astropy.modeling.polynomial.Chebyshev2D", "astropy.modeling.polynomial.Hermite1D", "astropy.modeling.polynomial.Hermite2D", ) def to_yaml_tree_transform(self, model, tag, ctx): poly_type = _CLASS_NAME_TO_POLY_INFO[model.__class__.__name__][0] if model.n_inputs == 1: coefficients = np.array(model.parameters) else: coefficients = np.zeros((model.x_degree + 1, model.y_degree + 1)) for i in range(model.x_degree + 1): for j in range(model.y_degree + 1): name = f"c{i}_{j}" coefficients[i, j] = getattr(model, name).value node = {"polynomial_type": poly_type, "coefficients": coefficients} if model.n_inputs == 1: if model.domain is not None: node["domain"] = model.domain if model.window is not None: node["window"] = model.window else: if model.x_domain or model.y_domain is not None: node["domain"] = (model.x_domain, model.y_domain) if model.x_window or model.y_window is not None: node["window"] = (model.x_window, model.y_window) return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling import polynomial coefficients = np.asarray(node["coefficients"]) poly_type = node["polynomial_type"] n_dim = coefficients.ndim class_name = _POLY_INFO_TO_CLASS_NAME[(poly_type, n_dim)] model_type = getattr(polynomial, class_name) coefficients = np.asarray(node["coefficients"]) if n_dim == 1: domain = node.get("domain", None) window = node.get("window", None) model = model_type(coefficients.size - 1, domain=domain, window=window) model.parameters = coefficients elif n_dim == 2: # noqa: PLR2004 x_domain, y_domain = tuple(node.get("domain", (None, None))) x_window, y_window = tuple(node.get("window", (None, None))) coeffs = {} shape = coefficients.shape x_degree = shape[0] - 1 y_degree = shape[1] - 1 for i in range(x_degree + 1): for j in range(y_degree + 1): name = f"c{i}_{j}" coeffs[name] = coefficients[i, j] model = model_type( x_degree, y_degree, x_domain=x_domain, y_domain=y_domain, x_window=x_window, y_window=y_window, **coeffs, ) else: msg = "astropy supports only 1D or 2D polynomial models" raise NotImplementedError(msg) return model asdf-astropy-0.5.0/asdf_astropy/converters/transform/projections.py000066400000000000000000000050431452515624200257540ustar00rootroot00000000000000from .core import TransformConverterBase, import_type, parameter_to_value class ProjectionConverter(TransformConverterBase): """ ASDF support for serializing most projection models. An instance of this class must be created for each projection. Parameters ---------- tags : list of str Tag patterns. pix2sky_type_name : str Fully-qualified type name of the projection's Pix2Sky model. sky2pix_type_name : str Fully-qualified type name of the projection's Sky2Pix model. """ def __init__(self, tags, pix2sky_type_name, sky2pix_type_name): self._tags = tags self._pix2sky_type_name = pix2sky_type_name self._sky2pix_type_name = sky2pix_type_name self._pix2sky_type = None self._sky2pix_type = None @property def tags(self): return self._tags @property def types(self): return [self._pix2sky_type_name, self._sky2pix_type_name] @property def pix2sky_type(self): # Delay import until the model class is needed to improve speed # of loading the extension. if self._pix2sky_type is None: self._pix2sky_type = import_type(self._pix2sky_type_name) return self._pix2sky_type @property def sky2pix_type(self): # Delay import until the model class is needed to improve speed # of loading the extension. if self._sky2pix_type is None: self._sky2pix_type = import_type(self._sky2pix_type_name) return self._sky2pix_type def to_yaml_tree_transform(self, model, tag, ctx): if isinstance(model, self.pix2sky_type): direction = "pix2sky" elif isinstance(model, self.sky2pix_type): direction = "sky2pix" else: msg = f"Unrecognized projection model type: {type(model)}" raise TypeError(msg) node = {p: parameter_to_value(getattr(model, p)) for p in model.param_names} node["direction"] = direction return node def from_yaml_tree_transform(self, node, tag, ctx): if node["direction"] == "pix2sky": model_type = self.pix2sky_type elif node["direction"] == "sky2pix": model_type = self.sky2pix_type else: msg = f"Unrecognized projection direction: {node['direction']}" raise ValueError(msg) model_kwargs = {} for param in model_type.param_names: if param in node: model_kwargs[param] = node[param] return model_type(**model_kwargs) asdf-astropy-0.5.0/asdf_astropy/converters/transform/properties.py000066400000000000000000000064721452515624200256200ustar00rootroot00000000000000from asdf.extension import Converter from astropy.utils import minversion class ModelBoundingBoxConverter(Converter): tags = ("tag:stsci.edu:asdf/transform/property/bounding_box-1.0.0",) types = ("astropy.modeling.bounding_box.ModelBoundingBox",) def to_yaml_tree(self, bbox, tag, ctx): return { "intervals": {_input: list(interval) for _input, interval in bbox.named_intervals.items()}, "ignore": list(bbox.ignored_inputs), "order": bbox.order, } def from_yaml_tree(self, node, tag, ctx): from astropy.modeling.bounding_box import ModelBoundingBox, get_index, get_name intervals = {_input: tuple(interval) for _input, interval in node["intervals"].items()} ignored = node["ignore"] if "ignore" in node else [] order = node["order"] if "order" in node else "C" def create_bounding_box(model, cbbox=None): if cbbox is None: ignore = ignored else: # Hack to pass compound_bounding_box selector_args ignore in 5.0.4+ ignore = list( set(ignored + [get_name(model, get_index(model, key)) for key in cbbox.selector_args.ignore]), ) # Add in globally ignored inputs from the compound_bounding_box in 5.1+ if minversion("astropy", "5.1"): ignore = list(set(ignore + [get_name(model, get_index(model, key)) for key in cbbox.ignored])) return ModelBoundingBox(intervals, model, ignored=ignore, order=order) return create_bounding_box class CompoundBoundingBoxConverter(Converter): tags = ("tag:stsci.edu:asdf/transform/property/compound_bounding_box-1.0.0",) types = ("astropy.modeling.bounding_box.CompoundBoundingBox",) def to_yaml_tree(self, cbbox, tag, ctx): node = { "selector_args": [{"argument": sa.name(cbbox._model), "ignore": sa.ignore} for sa in cbbox.selector_args], "cbbox": [{"key": list(key), "bbox": bbox} for key, bbox in cbbox.bounding_boxes.items()], "order": cbbox.order, } if minversion("astropy", "5.1"): node["ignore"] = cbbox.ignored_inputs return node def from_yaml_tree(self, node, tag, ctx): from astropy.modeling.bounding_box import CompoundBoundingBox selector_args = tuple((selector["argument"], selector["ignore"]) for selector in node["selector_args"]) bboxes = {tuple(bbox["key"]): bbox["bbox"] for bbox in node["cbbox"]} ignored = node["ignore"] if "ignore" in node else [] order = node["order"] if "order" in node else "C" def create_bounding_box(model): if not minversion("astropy", "5.1"): if len(ignored) > 0: msg = ( "Deserializing ignored elements of a compound bounding box is only supported for astropy 5.1+." ) raise RuntimeError(msg) cbbox = CompoundBoundingBox({}, model, selector_args, order=order) else: cbbox = CompoundBoundingBox({}, model, selector_args, ignored=ignored, order=order) for key, bb in bboxes.items(): cbbox[key] = bb(model, cbbox) return cbbox return create_bounding_box asdf-astropy-0.5.0/asdf_astropy/converters/transform/rotations.py000066400000000000000000000066101452515624200254400ustar00rootroot00000000000000from .core import TransformConverterBase, parameter_to_value class Rotate3DConverter(TransformConverterBase): """ ASDF support for serializing rotation models that use the rotate3d tag. """ tags = ("tag:stsci.edu:asdf/transform/rotate3d-*",) types = ( "astropy.modeling.rotations.RotateNative2Celestial", "astropy.modeling.rotations.RotateCelestial2Native", "astropy.modeling.rotations.EulerAngleRotation", ) def to_yaml_tree_transform(self, model, tag, ctx): from astropy.modeling import rotations if isinstance(model, rotations.RotateNative2Celestial): node = { "phi": parameter_to_value(model.lon), "theta": parameter_to_value(model.lat), "psi": parameter_to_value(model.lon_pole), "direction": "native2celestial", } elif isinstance(model, rotations.RotateCelestial2Native): node = { "phi": parameter_to_value(model.lon), "theta": parameter_to_value(model.lat), "psi": parameter_to_value(model.lon_pole), "direction": "celestial2native", } else: node = { "phi": parameter_to_value(model.phi), "theta": parameter_to_value(model.theta), "psi": parameter_to_value(model.psi), "direction": model.axes_order, } return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling import rotations if node["direction"] == "native2celestial": return rotations.RotateNative2Celestial(node["phi"], node["theta"], node["psi"]) if node["direction"] == "celestial2native": return rotations.RotateCelestial2Native(node["phi"], node["theta"], node["psi"]) return rotations.EulerAngleRotation(node["phi"], node["theta"], node["psi"], axes_order=node["direction"]) class RotationSequenceConverter(TransformConverterBase): """ ASDF support for serializing rotation sequence models. """ tags = ("tag:stsci.edu:asdf/transform/rotate_sequence_3d-*",) types = ( "astropy.modeling.rotations.RotationSequence3D", "astropy.modeling.rotations.SphericalRotationSequence", ) def to_yaml_tree_transform(self, model, tag, ctx): from astropy.modeling import rotations node = {"angles": list(model.angles.value)} node["axes_order"] = model.axes_order if isinstance(model, rotations.SphericalRotationSequence): node["rotation_type"] = "spherical" elif isinstance(model, rotations.RotationSequence3D): node["rotation_type"] = "cartesian" else: msg = f"Cannot serialize model of type {type(model)}" raise TypeError(msg) return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling import rotations angles = node["angles"] axes_order = node["axes_order"] rotation_type = node["rotation_type"] if rotation_type == "cartesian": return rotations.RotationSequence3D(angles, axes_order=axes_order) if rotation_type == "spherical": return rotations.SphericalRotationSequence(angles, axes_order=axes_order) msg = f"Unrecognized rotation_type: {rotation_type}" raise ValueError(msg) asdf-astropy-0.5.0/asdf_astropy/converters/transform/spline.py000066400000000000000000000012251452515624200247050ustar00rootroot00000000000000from .core import TransformConverterBase class SplineConverter(TransformConverterBase): """ ASDF support for serializing 1D spline models """ tags = ("tag:stsci.edu:asdf/transform/spline1d-*",) types = ("astropy.modeling.spline.Spline1D",) def to_yaml_tree_transform(self, model, tag, ctx): return {"knots": model.t, "coefficients": model.c, "degree": model.degree} def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling.spline import Spline1D knots = node["knots"] coeffs = node["coefficients"] degree = node["degree"] return Spline1D(knots, coeffs, degree) asdf-astropy-0.5.0/asdf_astropy/converters/transform/tabular.py000066400000000000000000000033471452515624200250540ustar00rootroot00000000000000from .core import TransformConverterBase __all__ = ["TabularConverter"] class TabularConverter(TransformConverterBase): """ ASDF support for serializing tabular models. """ tags = ("tag:stsci.edu:asdf/transform/tabular-*",) types = ( "astropy.modeling.tabular.Tabular1D", "astropy.modeling.tabular.Tabular2D", ) def to_yaml_tree_transform(self, model, tag, ctx): node = {} if model.fill_value is not None: node["fill_value"] = model.fill_value node["lookup_table"] = model.lookup_table node["points"] = list(model.points) node["method"] = str(model.method) node["bounds_error"] = model.bounds_error return node def from_yaml_tree_transform(self, node, tag, ctx): from astropy.modeling import tabular lookup_table = node.pop("lookup_table") dim = lookup_table.ndim fill_value = node.pop("fill_value", None) if dim == 1: points = (node["points"][0],) model = tabular.Tabular1D( points=points, lookup_table=lookup_table, method=node["method"], bounds_error=node["bounds_error"], fill_value=fill_value, ) elif dim == 2: # noqa: PLR2004 points = tuple(node["points"]) model = tabular.Tabular2D( points=points, lookup_table=lookup_table, method=node["method"], bounds_error=node["bounds_error"], fill_value=fill_value, ) else: msg = "tabular models with ndim > 2 are not supported " raise NotImplementedError(msg) return model asdf-astropy-0.5.0/asdf_astropy/converters/transform/tests/000077500000000000000000000000001452515624200242035ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/transform/tests/__init__.py000066400000000000000000000000001452515624200263020ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/transform/tests/test_transform.py000066400000000000000000001103611452515624200276310ustar00rootroot00000000000000import itertools import unittest.mock as mk import warnings import asdf import astropy import astropy.modeling import numpy as np import pytest from asdf.testing.helpers import yaml_to_asdf from astropy import units as u from astropy.modeling import models as astropy_models from astropy.modeling.bounding_box import CompoundBoundingBox, ModelBoundingBox from astropy.utils import minversion from asdf_astropy import integration from asdf_astropy.testing import helpers def assert_bounding_box_roundtrip(bounding_box, tmp_path, version=None): message = ( "asdf_astropy.converters.transforms.tests.test_transform.assert_bounding_box_round_trip is deprecated." "Use asdf_astropy.testing.helpers.assert_bounding_box_roundtrip instead." ) warnings.warn(message, DeprecationWarning, stacklevel=2) return helpers.assert_bounding_box_roundtrip(bounding_box, tmp_path, version=version) def assert_model_roundtrip(model, tmp_path, version=None): message = ( "asdf_astropy.converters.transforms.tests.test_transform.assert_model_round_trip is deprecated." "Use asdf_astropy.testing.helpers.assert_model_roundtrip instead." ) warnings.warn(message, DeprecationWarning, stacklevel=2) return helpers.assert_model_roundtrip(model, tmp_path, version=version) @pytest.mark.skipif( not minversion("asdf_transform_schemas", "0.2.2", inclusive=False), reason="Schema not present until versions after asdf-transform-schemas 0.2.2", ) def test_deprecations(tmp_path): # Test assert_bounding_box_roundtrip deprecation bbox = ModelBoundingBox((0, 1), astropy_models.Polynomial1D(1)) with pytest.warns(DeprecationWarning, match=".*assert_bounding_box_roundtrip.*"): assert_bounding_box_roundtrip(bbox, tmp_path) # Test assert_model_roundtrip deprecation model = astropy_models.Gaussian2D() with pytest.warns(DeprecationWarning, match=".*assert_model_roundtrip.*"): assert_model_roundtrip(model, tmp_path) def create_bounding_boxes(): model_bounding_box = [ ModelBoundingBox((0, 1), astropy_models.Polynomial1D(1)), ModelBoundingBox(((2, 3), (3, 4)), astropy_models.Polynomial2D(1)), ModelBoundingBox(((5, 6), (7, 8)), astropy_models.Polynomial2D(1), order="F"), ] # ignore option is not properly supported until astropy versions with the bug fix from: # astropy/astropy#13032 (milestone 5.0.5) is included if minversion("astropy", "5.0.4", inclusive=False): model_bounding_box.extend( [ ModelBoundingBox((9, 10), astropy_models.Polynomial2D(1), ignored=["x"]), ModelBoundingBox((11, 12), astropy_models.Polynomial2D(1), ignored=["y"]), ], ) compound_bounding_box = [ CompoundBoundingBox({(1,): (0, 1), (2,): (2, 3)}, astropy_models.Polynomial2D(1), [("x", True)]), CompoundBoundingBox( {(1,): ((0, 1), (-1, 0)), (2,): ((2, 3), (-3, -2))}, astropy_models.Polynomial2D(1), [("x", False)], ), ] if minversion("astropy", "5.1"): compound_bounding_box.extend( [ CompoundBoundingBox( {(1,): (0, 1), (2,): (2, 3)}, astropy_models.Polynomial2D(1), [("x", False)], ignored=["x"], ), CompoundBoundingBox( {(1,): (0, 1), (2,): (2, 3)}, astropy_models.Polynomial2D(1), [("x", False)], ignored=["y"], ), ], ) return model_bounding_box + compound_bounding_box @pytest.mark.parametrize("bbox", create_bounding_boxes()) @pytest.mark.skipif( not minversion("asdf_transform_schemas", "0.2.2", inclusive=False), reason="Schema not present until versions after asdf-transform-schemas 0.2.2", ) def test_round_trip_bounding_box(bbox, tmp_path): helpers.assert_bounding_box_roundtrip(bbox, tmp_path) def create_single_models(): # noqa: PLR0915 model_with_bounding_box = astropy_models.Shift(10) model_with_bounding_box.bounding_box = ((1, 7),) model_with_user_inverse = astropy_models.Shift(10) model_with_user_inverse.inverse = astropy_models.Shift(-7) model_with_constraints = astropy_models.Legendre2D( x_degree=1, y_degree=1, c0_0=1, c0_1=2, c1_0=3, fixed={"c1_0": True, "c0_1": True}, bounds={"c0_0": (-10, 10)}, ) model_with_custom_inputs_outputs = astropy_models.Gaussian2D() model_with_custom_inputs_outputs.inputs = ("a", "b") model_with_custom_inputs_outputs.outputs = ("c",) result = [ # Generic model features astropy_models.Shift(10, name="some model name"), model_with_bounding_box, model_with_user_inverse, model_with_constraints, model_with_custom_inputs_outputs, # astropy.modeling.functional_models astropy_models.AiryDisk2D(amplitude=10.0, x_0=0.5, y_0=1.5), astropy_models.Box1D(amplitude=10.0, x_0=0.5, width=5.0), astropy_models.Box2D(amplitude=10.0, x_0=0.5, x_width=5.0, y_0=1.5, y_width=7.0), astropy_models.Const1D(amplitude=5.0), astropy_models.Const2D(amplitude=5.0), astropy_models.Disk2D(amplitude=10.0, x_0=0.5, y_0=1.5, R_0=5.0), astropy_models.Ellipse2D(amplitude=10.0, x_0=0.5, y_0=1.5, a=2.0, b=4.0, theta=0.1), astropy_models.Exponential1D(amplitude=10.0, tau=3.5), astropy_models.Gaussian1D(amplitude=10.0, mean=5.0, stddev=3.0), astropy_models.Gaussian2D(amplitude=10.0, x_mean=5.0, y_mean=5.0, x_stddev=3.0, y_stddev=3.0), astropy_models.KingProjectedAnalytic1D(amplitude=10.0, r_core=5.0, r_tide=2.0), astropy_models.Linear1D(slope=2.0, intercept=1.5), astropy_models.Logarithmic1D(amplitude=10.0, tau=3.5), astropy_models.Lorentz1D(amplitude=10.0, x_0=0.5, fwhm=2.5), astropy_models.Moffat1D(amplitude=10.0, x_0=0.5, gamma=1.2, alpha=2.5), astropy_models.Moffat2D(amplitude=10.0, x_0=0.5, y_0=1.5, gamma=1.2, alpha=2.5), astropy_models.Multiply(3), astropy_models.Multiply(10 * u.m), astropy_models.Planar2D(slope_x=0.5, slope_y=1.2, intercept=2.5), astropy_models.RedshiftScaleFactor(z=2.5), astropy_models.RickerWavelet1D(amplitude=10.0, x_0=0.5, sigma=1.2), astropy_models.RickerWavelet2D(amplitude=10.0, x_0=0.5, y_0=1.5, sigma=1.2), astropy_models.Ring2D(amplitude=10.0, x_0=0.5, y_0=1.5, r_in=5.0, width=10.0), astropy_models.Scale(3.4), astropy_models.Sersic1D(amplitude=10.0, r_eff=1.0, n=4.0), astropy_models.Sersic2D(amplitude=10.0, r_eff=1.0, n=4.0, x_0=0.5, y_0=1.5, ellip=0.0, theta=0.0), astropy_models.Shift(2.0), astropy_models.Shift(2.0 * u.deg), astropy_models.Scale(3.4 * u.deg), astropy_models.Sine1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.Cosine1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.Tangent1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.ArcSine1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.ArcCosine1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.ArcTangent1D(amplitude=10.0, frequency=0.5, phase=1.0), astropy_models.Trapezoid1D(amplitude=10.0, x_0=0.5, width=5.0, slope=1.0), astropy_models.TrapezoidDisk2D(amplitude=10.0, x_0=0.5, y_0=1.5, R_0=5.0, slope=1.0), astropy_models.Voigt1D(x_0=0.55, amplitude_L=10.0, fwhm_L=0.5, fwhm_G=0.9), # astropy.modeling.mappings astropy_models.Identity(2), astropy_models.Mapping((0, 1), n_inputs=3), # astropy.modeling.math_functions astropy.modeling.math_functions.AbsoluteUfunc(), astropy.modeling.math_functions.AddUfunc(), astropy.modeling.math_functions.ArccosUfunc(), astropy.modeling.math_functions.ArccoshUfunc(), astropy.modeling.math_functions.ArcsinUfunc(), astropy.modeling.math_functions.ArcsinhUfunc(), astropy.modeling.math_functions.Arctan2Ufunc(), astropy.modeling.math_functions.ArctanUfunc(), astropy.modeling.math_functions.ArctanhUfunc(), astropy.modeling.math_functions.CbrtUfunc(), astropy.modeling.math_functions.CosUfunc(), astropy.modeling.math_functions.CoshUfunc(), astropy.modeling.math_functions.Deg2radUfunc(), astropy.modeling.math_functions.DivideUfunc(), astropy.modeling.math_functions.DivmodUfunc(), astropy.modeling.math_functions.Exp2Ufunc(), astropy.modeling.math_functions.ExpUfunc(), astropy.modeling.math_functions.Expm1Ufunc(), astropy.modeling.math_functions.FabsUfunc(), astropy.modeling.math_functions.Floor_divideUfunc(), astropy.modeling.math_functions.FmodUfunc(), astropy.modeling.math_functions.HypotUfunc(), astropy.modeling.math_functions.Log10Ufunc(), astropy.modeling.math_functions.Log1pUfunc(), astropy.modeling.math_functions.Log2Ufunc(), astropy.modeling.math_functions.LogUfunc(), astropy.modeling.math_functions.Logaddexp2Ufunc(), astropy.modeling.math_functions.LogaddexpUfunc(), astropy.modeling.math_functions.ModUfunc(), astropy.modeling.math_functions.MultiplyUfunc(), astropy.modeling.math_functions.NegativeUfunc(), astropy.modeling.math_functions.PositiveUfunc(), astropy.modeling.math_functions.PowerUfunc(), astropy.modeling.math_functions.Rad2degUfunc(), astropy.modeling.math_functions.ReciprocalUfunc(), astropy.modeling.math_functions.RemainderUfunc(), astropy.modeling.math_functions.RintUfunc(), astropy.modeling.math_functions.SinUfunc(), astropy.modeling.math_functions.SinhUfunc(), astropy.modeling.math_functions.SqrtUfunc(), astropy.modeling.math_functions.SquareUfunc(), astropy.modeling.math_functions.SubtractUfunc(), astropy.modeling.math_functions.TanUfunc(), astropy.modeling.math_functions.TanhUfunc(), astropy.modeling.math_functions.True_divideUfunc(), # astropy.modeling.physical_models astropy_models.BlackBody(scale=10.0, temperature=6000.0 * u.K), astropy_models.Drude1D(amplitude=10.0, x_0=0.5, fwhm=2.5), # TODO: NFW # astropy.modeling.polynomial astropy_models.Chebyshev1D(2, c0=2, c1=3, c2=0.5), astropy_models.Chebyshev1D(2, c0=2, c1=3, c2=0.5, domain=(0.0, 1.0), window=(1.5, 2.5)), astropy_models.Chebyshev2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astropy_models.Chebyshev2D( 1, 1, c0_0=1, c0_1=2, c1_0=3, x_domain=(1.0, 2.0), y_domain=(3.0, 4.0), x_window=(5.0, 6.0), y_window=(7.0, 8.0), ), astropy_models.Hermite1D(2, c0=2, c1=3, c2=0.5), astropy_models.Hermite2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astropy_models.Legendre1D(2, c0=2, c1=3, c2=0.5), astropy_models.Legendre2D(1, 1, c0_0=1, c0_1=2, c1_0=3), astropy_models.Polynomial1D(2, c0=1, c1=2, c2=3), astropy_models.Polynomial2D(1, c0_0=1, c0_1=2, c1_0=3), # astropy.modeling.spline astropy_models.Spline1D( np.array([-3.0, -3.0, -3.0, -3.0, -1.0, 0.0, 1.0, 3.0, 3.0, 3.0, 3.0]), np.array( [ 0.10412331, 0.07013616, -0.18799552, 1.35953147, -0.15282581, 0.03923, -0.04297299, 0.0, 0.0, 0.0, 0.0, ], ), 3, ), # astropy.modeling.powerlaws astropy_models.BrokenPowerLaw1D(amplitude=10, x_break=0.5, alpha_1=2.0, alpha_2=3.5), astropy_models.ExponentialCutoffPowerLaw1D(10, 0.5, 2.0, 7.0), astropy_models.LogParabola1D( amplitude=10, x_0=0.5, alpha=2.0, beta=3.0, ), astropy_models.PowerLaw1D(amplitude=10.0, x_0=0.5, alpha=2.0), astropy_models.SmoothlyBrokenPowerLaw1D(amplitude=10.0, x_break=5.0, alpha_1=2.0, alpha_2=3.0, delta=0.5), # astropy.modeling.projections astropy_models.AffineTransformation2D( matrix=np.array([[1.0, 2.0], [3.0, 4.0]]), translation=np.array([5.0, 6.0]), ), astropy_models.Pix2Sky_Airy(theta_b=75.8), astropy_models.Sky2Pix_Airy(theta_b=75.8), astropy_models.Pix2Sky_BonneEqualArea(theta1=44.3), astropy_models.Sky2Pix_BonneEqualArea(theta1=44.3), astropy_models.Pix2Sky_COBEQuadSphericalCube(), astropy_models.Sky2Pix_COBEQuadSphericalCube(), astropy_models.Pix2Sky_ConicEqualArea(sigma=89.5, delta=0.5), astropy_models.Sky2Pix_ConicEqualArea(sigma=89.5, delta=0.5), astropy_models.Pix2Sky_ConicEquidistant(sigma=89.5, delta=0.5), astropy_models.Sky2Pix_ConicEquidistant(sigma=89.5, delta=0.5), astropy_models.Pix2Sky_ConicOrthomorphic(sigma=88.0, delta=1.0), astropy_models.Sky2Pix_ConicOrthomorphic(sigma=88.0, delta=1.0), astropy_models.Pix2Sky_ConicPerspective(sigma=89.5, delta=0.5), astropy_models.Sky2Pix_ConicPerspective(sigma=89.5, delta=0.5), astropy_models.Pix2Sky_CylindricalEqualArea(lam=0.5), astropy_models.Sky2Pix_CylindricalEqualArea(lam=0.5), astropy_models.Pix2Sky_CylindricalPerspective(mu=1.5, lam=2.4), astropy_models.Sky2Pix_CylindricalPerspective(mu=1.5, lam=2.4), astropy_models.Pix2Sky_Gnomonic(), astropy_models.Sky2Pix_Gnomonic(), astropy_models.Pix2Sky_HEALPixPolar(), astropy_models.Sky2Pix_HEALPixPolar(), astropy_models.Pix2Sky_HEALPix(H=12.0, X=17.0), astropy_models.Sky2Pix_HEALPix(H=12.0, X=17.0), astropy_models.Pix2Sky_HammerAitoff(), astropy_models.Sky2Pix_HammerAitoff(), astropy_models.Pix2Sky_Mercator(), astropy_models.Sky2Pix_Mercator(), astropy_models.Pix2Sky_Molleweide(), astropy_models.Sky2Pix_Molleweide(), astropy_models.Pix2Sky_Parabolic(), astropy_models.Sky2Pix_Parabolic(), astropy_models.Pix2Sky_PlateCarree(), astropy_models.Sky2Pix_PlateCarree(), astropy_models.Pix2Sky_Polyconic(), astropy_models.Sky2Pix_Polyconic(), astropy_models.Pix2Sky_QuadSphericalCube(), astropy_models.Sky2Pix_QuadSphericalCube(), astropy_models.Pix2Sky_SansonFlamsteed(), astropy_models.Sky2Pix_SansonFlamsteed(), astropy_models.Pix2Sky_SlantOrthographic(xi=0.1, eta=0.2), astropy_models.Sky2Pix_SlantOrthographic(xi=0.1, eta=0.2), astropy_models.Pix2Sky_SlantZenithalPerspective(mu=1.5, phi0=15.0, theta0=80.0), astropy_models.Sky2Pix_SlantZenithalPerspective(mu=1.5, phi0=15.0, theta0=80.0), astropy_models.Pix2Sky_Stereographic(), astropy_models.Sky2Pix_Stereographic(), astropy_models.Pix2Sky_TangentialSphericalCube(), astropy_models.Sky2Pix_TangentialSphericalCube(), astropy_models.Pix2Sky_ZenithalEqualArea(), astropy_models.Sky2Pix_ZenithalEqualArea(), astropy_models.Pix2Sky_ZenithalEquidistant(), astropy_models.Sky2Pix_ZenithalEquidistant(), astropy_models.Pix2Sky_ZenithalPerspective(mu=1.5, gamma=15.0), astropy_models.Sky2Pix_ZenithalPerspective(mu=1.5, gamma=15.0), # astropy.modeling.rotations astropy_models.EulerAngleRotation(23, 14, 2.3, axes_order="xzx"), astropy_models.RotateCelestial2Native(5.63, -72.5, 180), astropy_models.RotateCelestial2Native(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg), astropy_models.RotateNative2Celestial(5.63, -72.5, 180), astropy_models.RotateNative2Celestial(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg), astropy_models.Rotation2D(angle=1.51), astropy_models.RotationSequence3D([1.2, 2.3, 3.4, 0.3], "xyzx"), astropy_models.SphericalRotationSequence([1.2, 2.3, 3.4, 0.3], "xyzy"), # astropy.modeling.tabular astropy_models.Tabular1D(points=np.arange(0, 5), lookup_table=[1.0, 10, 2, 45, -3]), astropy_models.Tabular1D(points=np.arange(0, 5) * u.pix, lookup_table=[1.0, 10, 2, 45, -3] * u.nm), astropy_models.Tabular2D( points=([1, 2, 3], [1, 2, 3]), lookup_table=np.arange(0, 9).reshape(3, 3), bounds_error=False, fill_value=None, method="nearest", ), astropy_models.Tabular2D( points=([1, 2, 3], [1, 2, 3]) * u.pix, lookup_table=np.arange(0, 9).reshape(3, 3) * u.nm, bounds_error=False, fill_value=None, method="nearest", ), ] # Test case for model with a metaclass generated abstract bounding_box # where a custom bounding box is stored gaussian_1d = astropy_models.Gaussian1D(10, 1.5, 0.25) gaussian_1d.bounding_box = [7, 8] result.append(gaussian_1d) # compound model with bounding box model = astropy_models.Shift(1) & astropy_models.Shift(2) model.bounding_box = ((1, 2), (3, 4)) result.append(model) # compound model with bounding box model = astropy_models.Shift(1) & astropy_models.Shift(2) & astropy_models.Shift(3) model.bounding_box = ((1, 2), (3, 4), (5, 6)) result.append(model) if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): # model with compound bounding box model = astropy_models.Shift(1) & astropy_models.Scale(2) & astropy_models.Identity(1) model.inputs = ("x", "y", "slit_id") bounding_boxes = { (0,): ((-0.5, 1047.5), (-0.5, 2047.5)), (1,): ((-0.5, 3047.5), (-0.5, 4047.5)), } bounding_box = CompoundBoundingBox.validate(model, bounding_boxes, selector_args=[("slit_id", True)], order="F") model.bounding_box = bounding_box result.append(model) model = astropy_models.Shift(1) & astropy_models.Shift(2) & astropy_models.Shift(3) model.inputs = ("x", "y", "z") bounding_boxes = { (0,): (1.0, 2.0), (1,): (3.0, 4.0), } if minversion("astropy", "5.1"): bounding_box = CompoundBoundingBox.validate( model, bounding_boxes, selector_args=[("x", True)], ignored=["y"], ) model.bounding_box = bounding_box result.append(model) result.append(astropy_models.Plummer1D(mass=10.0, r_plum=5.0)) # models with input_units_equivalencies # 1D model m1 = astropy_models.Shift(1 * u.kg) m1.input_units_equivalencies = {"x": u.mass_energy()} # 2D model m2 = astropy_models.Const2D(10 * u.Hz) m2.input_units_equivalencies = {"x": u.dimensionless_angles(), "y": u.dimensionless_angles()} # 2D model with only one input equivalencies m3 = astropy_models.Const2D(10 * u.Hz) m3.input_units_equivalencies = {"x": u.dimensionless_angles()} # model using equivalency that has args using units m4 = astropy_models.PowerLaw1D(amplitude=1 * u.m, x_0=10 * u.pix, alpha=7) m4.input_units_equivalencies = {"x": u.equivalencies.pixel_scale(0.5 * u.arcsec / u.pix)} result.extend([m1, m2, m3, m4]) # compound models with input_units_equivalencies m1 = astropy_models.Gaussian1D(10 * u.K, 11 * u.arcsec, 12 * u.arcsec) m1.input_units_equivalencies = {"x": u.parallax()} m2 = astropy_models.Gaussian1D(5 * u.s, 2 * u.K, 3 * u.K) m2.input_units_equivalencies = {"x": u.temperature()} result.extend([m1 | m2, m1 & m2, m1 + m2]) # fix_inputs models with input_units_equivalencies m1 = astropy_models.Pix2Sky_TAN() m1.input_units_equivalencies = {"x": u.dimensionless_angles(), "y": u.dimensionless_angles()} m2 = astropy_models.Rotation2D() m = m1 | m2 result.extend([astropy_models.fix_inputs(m, {"x": 45}), astropy_models.fix_inputs(m, {0: 45})]) if minversion("astropy", "5.1") and minversion("asdf-transform-schemas", "0.2.3"): result.append(astropy_models.Schechter1D(phi_star=1.0, m_star=2.0, alpha=3.0)) return result UNSUPPORTED_MODELS = [ # FITS-specific and deemed unworthy of ASDF serialization: astropy.modeling.polynomial.InverseSIP, astropy.modeling.polynomial.SIP, # Base classes which should not be directly supported: astropy.modeling.core.Model, astropy.modeling.math_functions._NPUfuncModel, astropy.modeling.polynomial.OrthoPolynomialBase, astropy.modeling.polynomial.PolynomialModel, astropy.modeling.projections.Conic, astropy.modeling.projections.Cylindrical, astropy.modeling.projections.HEALPix, astropy.modeling.projections.Pix2SkyProjection, astropy.modeling.projections.Projection, astropy.modeling.projections.PseudoConic, astropy.modeling.projections.PseudoCylindrical, astropy.modeling.projections.QuadCube, astropy.modeling.projections.Sky2PixProjection, astropy.modeling.projections.Zenithal, # https://github.com/astropy/asdf-astropy/issues/6 astropy.modeling.physical_models.NFW, ] if minversion("astropy", "5.1") and not minversion("asdf-transform-schemas", "0.2.3"): UNSUPPORTED_MODELS.append(astropy.modeling.powerlaws.Schechter1D) # Model added in astropy 5.1 and schema added after asdf-transform 0.2.2 if minversion("astropy", "5.1") and not minversion("asdf_transform_schemas", "0.2.2", inclusive=False): UNSUPPORTED_MODELS.append(astropy.modeling.powerlaws.Schechter1D) if minversion("astropy", "6.0.dev"): UNSUPPORTED_MODELS.append(astropy.modeling.functional_models.GeneralSersic2D) @pytest.mark.parametrize("model", create_single_models()) def test_single_model(tmp_path, model): helpers.assert_model_roundtrip(model, tmp_path) def get_all_models(): def _iterate_model_classes(): for _key, value in itertools.chain( astropy_models.__dict__.items(), astropy.modeling.math_functions.__dict__.items(), ): if ( isinstance(value, type) and issubclass(value, astropy.modeling.core.Model) and value not in UNSUPPORTED_MODELS ): yield value return list(_iterate_model_classes()) @pytest.mark.parametrize("model", get_all_models()) def test_all_models_supported(model): """ Test that all model classes in astropy have serialization support implemented in this package. If this test fails, file an issue on GitHub for each missing model and add the model to the UNSUPPORTED_MODELS list above with a link to the issue in a comment. """ extensions = integration.get_extensions() extension_manager = asdf.extension.ExtensionManager(extensions) message = f"Missing support for model: {model.__module__}.{model.__qualname__}" assert extension_manager.handles_type(model), message def test_legacy_const(tmp_path): with asdf.config_context() as config: config.remove_extension("asdf://asdf-format.org/transform/extensions/transform-1.5.0") model = astropy_models.Const1D(amplitude=5.0) helpers.assert_model_roundtrip(model, tmp_path, version="1.3.0") model = astropy_models.Const2D(amplitude=5.0) with pytest.raises(TypeError, match=r".* does not support models with > 1 dimension"): helpers.assert_model_roundtrip(model, tmp_path, version="1.3.0") COMPOUND_OPERATORS = [ "__add__", "__sub__", "__mul__", "__truediv__", "__pow__", "__or__", "__and__", ] @pytest.mark.parametrize("operator", COMPOUND_OPERATORS) def test_compound_model(tmp_path, operator): left_model = astropy_models.Shift(5) right_model = astropy_models.Shift(-1) model = getattr(left_model, operator)(right_model) result = helpers.assert_model_roundtrip(model, tmp_path) helpers.assert_model_equal(result.left, left_model) helpers.assert_model_equal(result.right, right_model) assert result.op == model.op def test_fix_inputs(tmp_path): model = astropy_models.Gaussian2D(1, 2, 3, 4, 5) fixed_model = astropy_models.fix_inputs(model, {"x": 2.5}) result = helpers.assert_model_roundtrip(fixed_model, tmp_path) helpers.assert_model_equal(result.left, model) assert result.right == fixed_model.right assert result.op == fixed_model.op def test_units_mapping(tmp_path): # Basic mapping between units: model = astropy_models.UnitsMapping(((u.m, u.dimensionless_unscaled),)) model.name = "foo" result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping # Remove units: model = astropy_models.UnitsMapping(((u.m, None),)) result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping # Change a model to accept any units: model = astropy_models.UnitsMapping(((None, u.m),)) result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping # With equivalencies: model = astropy_models.UnitsMapping( ((u.m, u.dimensionless_unscaled),), input_units_equivalencies={"x": u.equivalencies.spectral()}, ) result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping # Allow dimensionless on all inputs: model = astropy_models.UnitsMapping( ((u.m, u.dimensionless_unscaled), (u.s, u.Hz)), input_units_allow_dimensionless=True, ) result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping # Allow dimensionless selectively: model = astropy_models.UnitsMapping( ((u.m, u.dimensionless_unscaled), (u.s, u.Hz)), input_units_allow_dimensionless={"x0": True, "x1": False}, ) result = helpers.assert_model_roundtrip(model, tmp_path) assert result.mapping == model.mapping @pytest.mark.parametrize("standard_version", [v for v in asdf.versioning.supported_versions if v >= "1.4.0"]) @pytest.mark.parametrize( "model", [ astropy_models.Polynomial1D(1, c0=5, c1=17), astropy_models.Polynomial1D(1, c0=5, c1=17, domain=[-5, 4], window=[-2, 3]), astropy_models.Chebyshev1D(2, c0=2, c1=3, c2=0.5, domain=[-2, 2]), astropy_models.Chebyshev1D(2, c0=2, c1=3, c2=0.5, domain=[-2, 2], window=[-0.5, 0.5]), ], ) def test_1d_polynomial_with_asdf_standard_version(tmp_path, standard_version, model): result = helpers.assert_model_roundtrip(model, tmp_path, version=standard_version) assert result.domain == model.domain assert result.window == model.window @pytest.mark.parametrize("standard_version", [v for v in asdf.versioning.supported_versions if v >= "1.4.0"]) @pytest.mark.parametrize( "model", [ astropy_models.Polynomial2D(2, c0_0=3, c1_0=5, c0_1=7), astropy_models.Polynomial2D( 2, c0_0=3, c1_0=5, c0_1=7, x_domain=[-2, 2], y_domain=[-4, 4], x_window=[-6, 6], y_window=[-8, 8], ), astropy_models.Chebyshev2D(1, 1, c0_0=1, c0_1=2, c1_0=3, x_domain=[-2, 2], y_domain=[-2, 2]), astropy_models.Chebyshev2D( 1, 1, c0_0=1, c0_1=2, c1_0=3, x_domain=[-2, 2], y_domain=[-2, 2], x_window=[-0.5, 0.5], y_window=[-0.1, 0.5], ), ], ) def test_2d_polynomial_with_asdf_standard_version(tmp_path, standard_version, model): result = helpers.assert_model_roundtrip(model, tmp_path, version=standard_version) assert result.x_domain == model.x_domain assert result.y_domain == model.y_domain assert result.x_window == model.x_window assert result.y_window == model.y_window def test_deserialize_compound_user_inverse(tmp_path): """ Confirm that we are able to correctly reconstruct a compound model with a user inverse set on one of its component models. Due to code in TransformConverter that facilitates circular inverses, the user inverse of the component model is not available at the time that the CompoundModel is constructed. """ yaml = """ model: !transform/concatenate-1.2.0 forward: - !transform/shift-1.2.0 inverse: !transform/shift-1.2.0 {offset: 5.0} offset: -10.0 - !transform/shift-1.2.0 {offset: -20.0} """ buff = yaml_to_asdf(yaml) with asdf.open(buff) as af: model = af["model"] assert model.has_inverse() assert model.inverse(-5, -20) == (0, 0) def test_rotation_errors(): from asdf_astropy.converters.transform.rotations import RotationSequenceConverter converter = RotationSequenceConverter() # to yaml error mdl = astropy_models.Const1D(5) mdl.angles = mk.MagicMock() mdl.axes_order = mk.MagicMock() with pytest.raises(TypeError, match=r"Cannot serialize model of type *"): converter.to_yaml_tree_transform(mdl, mk.MagicMock(), mk.MagicMock()) # from yaml error node = {"angles": mk.MagicMock(), "axes_order": mk.MagicMock(), "rotation_type": mk.MagicMock()} with pytest.raises(ValueError, match=r"Unrecognized rotation_type: *"): converter.from_yaml_tree_transform(node, mk.MagicMock(), mk.MagicMock()) def test_projection_errors(): from asdf_astropy.converters.transform.projections import ProjectionConverter converter = ProjectionConverter(mk.MagicMock(), mk.MagicMock(), mk.MagicMock()) converter._sky2pix_type = astropy_models.Pix2Sky_Airy converter._pix2sky_type = astropy_models.Sky2Pix_Airy # to yaml error mdl = astropy_models.Const1D(5) with pytest.raises(TypeError, match=r"Unrecognized projection model type: *"): converter.to_yaml_tree_transform(mdl, mk.MagicMock(), mk.MagicMock()) # from yaml error node = {"direction": mk.MagicMock()} with pytest.raises(ValueError, match=r"Unrecognized projection direction: *"): converter.from_yaml_tree_transform(node, mk.MagicMock(), mk.MagicMock()) def test_polynomial_errors(): from asdf_astropy.converters.transform.polynomial import PolynomialConverter converter = PolynomialConverter() # from yaml error node = {"coefficients": np.zeros((2, 3))} with pytest.raises(TypeError, match=r"Coefficients must be an .* matrix"): converter.from_yaml_tree_transform(node, mk.MagicMock(), mk.MagicMock()) def test_compound_errors(): from asdf_astropy.converters.transform.compound import CompoundConverter converter = CompoundConverter() # Left not a model tag = "!transform/add-1.2.0" node = {"forward": [mk.MagicMock(), mk.MagicMock()]} with pytest.raises(TypeError, match=r"Unknown left model type '.*'"): converter.from_yaml_tree_transform(node, tag, mk.MagicMock()) # Right not a model (not fix_inputs) node = {"forward": [astropy_models.Const1D(17), mk.MagicMock()]} with pytest.raises(TypeError, match=r"Unknown right model type '.*'"): converter.from_yaml_tree_transform(node, tag, mk.MagicMock()) # Right not a model (fix_inputs) tag = "!transform/fix_inputs-1.2.0" mdl = mk.MagicMock() mdl.__class__ = astropy_models.Const1D node = {"forward": [astropy_models.Const1D(17), mdl]} with pytest.raises(TypeError, match=r"Unknown right model type '.*'"): converter.from_yaml_tree_transform(node, tag, mk.MagicMock()) @pytest.mark.skipif( not minversion("asdf_transform_schemas", "0.2.2", inclusive=False), reason="Schema not present until versions after asdf-transform-schemas 0.2.2", ) def test_bounding_box_missing_attributes(): yaml = """ model: !transform/constant-1.4.0 value: 1 dimensions: 1 bounding_box: !transform/property/bounding_box-1.0.0 intervals: x: [1.0, 2.0] """ buff = yaml_to_asdf(yaml) with asdf.open(buff) as af: model = af["model"] assert model.bounding_box.ignored == [] assert model.bounding_box.order == "C" yaml = """ model: !transform/constant-1.4.0 value: 1 dimensions: 2 bounding_box: !transform/property/compound_bounding_box-1.0.0 selector_args: - argument: x ignore: true cbbox: - key: [0] # value of input x is 0 to select this box bbox: !transform/property/bounding_box-1.0.0 intervals: y: [1.0, 2.0] - key: [3] # value of input x is 3 to select this box bbox: !transform/property/bounding_box-1.0.0 intervals: y: [4.0, 5.0] """ buff = yaml_to_asdf(yaml) with asdf.open(buff) as af: model = af["model"] assert model.bounding_box.order == "C" @pytest.mark.filterwarnings("ignore:Unable to locate schema file for.*") @pytest.mark.filterwarnings("ignore:.* not recognized, converting to raw Python data structure") def test_compound_bbox_ignored_error(): yaml = """ model: !transform/concatenate-1.2.0 forward: - !transform/concatenate-1.2.0 forward: - !transform/shift-1.2.0 offset: 1.0 - !transform/shift-1.2.0 offset: 2.0 - !transform/shift-1.2.0 offset: 3.0 bounding_box: !transform/property/compound_bounding_box-1.0.0 selector_args: - argument: x ignore: true cbbox: - key: [0] # both value of input x is 0 bbox: !transform/property/bounding_box-1.0.0 intervals: x0: [2.0, 3.0] - key: [1] # both value of input x is 1 bbox: !transform/property/bounding_box-1.0.0 intervals: x0: [6.0, 7.0] ignore: [x1] """ buff = yaml_to_asdf(yaml) if minversion("astropy", "5.1"): if not minversion("asdf_transform_schemas", "0.2.2", inclusive=False): with pytest.raises(TypeError, match=r"Cannot form bounding_box from: *"): asdf.open(buff) else: asdf.open(buff) elif not minversion("asdf_transform_schemas", "0.2.2", inclusive=False): with pytest.raises(TypeError, match=r"Cannot form bounding_box from: *"): asdf.open(buff) else: with pytest.raises( RuntimeError, match=r"Deserializing ignored elements of a compound bounding box is only supported for astropy 5.1+.", ): asdf.open(buff) def test_serialize_bbox(tmp_path): mdl = astropy_models.Const2D(3) if minversion("astropy", "5.1"): from astropy.modeling import bind_bounding_box bind_bounding_box(mdl, (1, 2), ignored="y") if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): helpers.assert_model_roundtrip(mdl, tmp_path) else: with pytest.raises( RuntimeError, match=r"asdf-transform-schemas > 0.2.2 in order to serialize a bounding_box with ignored", ): helpers.assert_model_roundtrip(mdl, tmp_path) else: bbox = ModelBoundingBox({"x": (1, 2)}, mdl, ignored=["y"]) mdl._user_bounding_box = bbox if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): with pytest.raises( RuntimeError, match=r"Bounding box ignored arguments are only supported by astropy 5.1+", ): helpers.assert_model_roundtrip(mdl, tmp_path) else: with pytest.raises( RuntimeError, match=r"asdf-transform-schemas > 0.2.2 in order to serialize a bounding_box with ignored", ): helpers.assert_model_roundtrip(mdl, tmp_path) def test_serialize_cbbox(tmp_path): mdl = astropy_models.Shift(1) & astropy_models.Scale(2) & astropy_models.Identity(1) mdl.inputs = ("x", "y", "slit_id") bounding_boxes = { (0,): ((-0.5, 1047.5), (-0.5, 2047.5)), (1,): ((-0.5, 3047.5), (-0.5, 4047.5)), } bounding_box = CompoundBoundingBox.validate(mdl, bounding_boxes, selector_args=[("slit_id", True)], order="F") mdl.bounding_box = bounding_box if minversion("asdf_transform_schemas", "0.2.2", inclusive=False): helpers.assert_model_roundtrip(mdl, tmp_path) else: with pytest.raises( RuntimeError, match=r"asdf-transform-schemas > 0.2.2 in order to serialize a compound bounding_box", ): helpers.assert_model_roundtrip(mdl, tmp_path) asdf-astropy-0.5.0/asdf_astropy/converters/unit/000077500000000000000000000000001452515624200220055ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/unit/__init__.py000066400000000000000000000004151452515624200241160ustar00rootroot00000000000000__all__ = [ "EquivalencyConverter", "QuantityConverter", "UnitConverter", "MagUnitConverter", ] from .equivalency import EquivalencyConverter from .magunit import MagUnitConverter from .quantity import QuantityConverter from .unit import UnitConverter asdf-astropy-0.5.0/asdf_astropy/converters/unit/equivalency.py000066400000000000000000000024251452515624200247070ustar00rootroot00000000000000from asdf.extension import Converter class EquivalencyConverter(Converter): tags = ("tag:astropy.org:astropy/units/equivalency-*",) types = ("astropy.units.equivalencies.Equivalency",) def to_yaml_tree(self, obj, tag, ctx): return [ {"name": name, "kwargs_names": list(kw.keys()), "kwargs_values": list(kw.values())} for name, kw in zip(obj.name, obj.kwargs) ] def from_yaml_tree(self, node, tag, ctx): from astropy.cosmology.units import with_H0 from astropy.units import equivalencies components = [] for equivalency_node in node: name = equivalency_node["name"] equivalency_method = with_H0 if name == "with_H0" else getattr(equivalencies, name) kwargs = dict(zip(equivalency_node["kwargs_names"], equivalency_node["kwargs_values"])) components.append(equivalency_method(**kwargs)) # The Equivalency class is a UserList that overrides __add__ to # provide special behavior when combined with another Equivalency. # We're using sum here to add each subsequent Equivalency to the # first so we end up with a single correctly combined Equivalency # object at the end. return sum(components[1:], components[0]) asdf-astropy-0.5.0/asdf_astropy/converters/unit/magunit.py000066400000000000000000000006141452515624200240240ustar00rootroot00000000000000from asdf.extension import Converter class MagUnitConverter(Converter): tags = ("tag:astropy.org:astropy/units/magunit-*",) types = ("astropy.units.function.logarithmic.MagUnit",) def to_yaml_tree(self, obj, tag, ctx): return {"unit": obj.physical_unit} def from_yaml_tree(self, node, tag, ctx): from astropy.units import mag return mag(node["unit"]) asdf-astropy-0.5.0/asdf_astropy/converters/unit/quantity.py000066400000000000000000000020651452515624200242400ustar00rootroot00000000000000from asdf.extension import Converter from asdf.tags.core.ndarray import NDArrayType class QuantityConverter(Converter): tags = ("tag:stsci.edu:asdf/unit/quantity-*",) types = ( "astropy.units.quantity.Quantity", # The Distance class has no tag of its own, so we # just serialize it as a quantity. "astropy.coordinates.distances.Distance", ) def to_yaml_tree(self, obj, tag, ctx): node = { "value": obj.value, "unit": obj.unit, } if obj.isscalar: node["datatype"] = obj.dtype.name return node def from_yaml_tree(self, node, tag, ctx): from astropy.units import Quantity value = node["value"] dtype = node.get("datatype", None) if isinstance(value, NDArrayType): # TODO: Why doesn't NDArrayType work? This needs some research # and documentation. value = value._make_array() dtype = value.dtype return Quantity(value, unit=node["unit"], copy=False, dtype=dtype) asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/000077500000000000000000000000001452515624200231475ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/__init__.py000066400000000000000000000000001452515624200252460ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/test_equivalency.py000066400000000000000000000034531452515624200271120ustar00rootroot00000000000000import asdf import astropy import pytest from astropy import units as u from astropy.cosmology import Planck15 from astropy.cosmology.units import with_H0 from astropy.units import equivalencies as eq from packaging.version import Version def create_equivalencies(): result = [ eq.plate_scale(0.3 * u.deg / u.mm), eq.pixel_scale(0.5 * u.deg / u.pix), eq.spectral_density(350 * u.nm, factor=2), eq.spectral_density(350 * u.nm), eq.spectral(), eq.brightness_temperature(500 * u.GHz), eq.brightness_temperature(500 * u.GHz, beam_area=23 * u.sr), with_H0(), eq.temperature_energy(), eq.temperature(), eq.thermodynamic_temperature(300 * u.Hz), eq.thermodynamic_temperature(140 * u.GHz, Planck15.Tcmb0), eq.beam_angular_area(3 * u.sr), eq.mass_energy(), eq.molar_mass_amu(), eq.doppler_relativistic(2 * u.m), eq.doppler_optical(2 * u.nm), eq.doppler_radio(2 * u.Hz), eq.parallax(), eq.logarithmic(), eq.dimensionless_angles(), eq.spectral() + eq.temperature(), (eq.spectral_density(35 * u.nm) + eq.brightness_temperature(5 * u.Hz, beam_area=2 * u.sr)), (eq.spectral() + eq.spectral_density(35 * u.nm) + eq.brightness_temperature(5 * u.Hz, beam_area=2 * u.sr)), ] if Version(astropy.__version__) >= Version("4.1"): result.append(eq.pixel_scale(100.0 * u.pix / u.cm)) return result @pytest.mark.parametrize("equivalency", create_equivalencies()) def test_serialization(equivalency, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["equivalency"] = equivalency af.write_to(file_path) with asdf.open(file_path) as af: assert af["equivalency"] == equivalency asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/test_magunit.py000066400000000000000000000033031452515624200262230ustar00rootroot00000000000000import asdf import pytest from astropy import units def create_builtin_units(): return {u for u in list(units.__dict__.values()) if isinstance(u, units.MagUnit)} @pytest.mark.parametrize("unit", create_builtin_units()) @pytest.mark.filterwarnings("ignore::astropy.units.core.UnitsWarning") def test_builtin_serialization(unit, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["unit"] = unit af.write_to(file_path) with asdf.open(file_path) as af: assert af["unit"].is_equivalent(unit) with asdf.open(file_path, _force_raw_types=True) as af: assert isinstance(af["unit"], asdf.tagged.TaggedDict) assert af["unit"]._tag.startswith("tag:astropy.org:astropy/units/magunit-") def create_magunits(): magunits = [] for u in units.__dict__.values(): if isinstance(u, units.UnitBase) and not isinstance(u, units.MagUnit): try: magunit = units.mag(u) except units.UnitConversionError: pass else: magunits.append(magunit) return frozenset(magunits) @pytest.mark.parametrize("unit", create_magunits()) @pytest.mark.filterwarnings("ignore::astropy.units.core.UnitsWarning") def test_magunit_serialization(unit, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["unit"] = unit af.write_to(file_path) with asdf.open(file_path) as af: assert af["unit"].is_equivalent(unit) with asdf.open(file_path, _force_raw_types=True) as af: assert isinstance(af["unit"], asdf.tagged.TaggedDict) assert af["unit"]._tag.startswith("tag:astropy.org:astropy/units/magunit-") asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/test_quantity.py000066400000000000000000000110211452515624200264310ustar00rootroot00000000000000import asdf import numpy as np import pytest from asdf.testing import helpers from astropy import units from astropy.units import Quantity from numpy.testing import assert_array_equal def create_quantities(): return [ # Scalar: Quantity(2.71828, units.kpc), # Non-float scalar: Quantity(7, units.K, dtype=np.int32), # Single element array: Quantity([3.14159], units.kg), # Multiple element array: Quantity([x * 2.3081 for x in range(10)], units.ampere), # Multiple dimension array: Quantity(np.arange(100, dtype=np.float64).reshape(5, 20), units.km), # Non-float array: Quantity(np.zeros((5, 5), dtype=np.uint16), units.cm, dtype=np.uint16), ] @pytest.mark.parametrize("quantity", create_quantities()) def test_serialization(quantity, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["quantity"] = quantity af.write_to(file_path) with asdf.open(file_path) as af: assert (af["quantity"].value == quantity.value).all() assert af["quantity"].dtype == quantity.dtype assert (af["quantity"] == quantity).all() def test_read_untagged_unit(): value = 2.71828 yaml = f""" quantity: !unit/quantity-1.1.0 value: {value} unit: kpc """ buff = helpers.yaml_to_asdf(yaml) with asdf.open(buff) as af: assert af["quantity"].value == value assert af["quantity"].unit.is_equivalent(units.kpc) def test_read_tagged_unit(): value = 2.71828 yaml = f""" quantity: !unit/quantity-1.1.0 value: {value} unit: !unit/unit-1.0.0 kpc """ buff = helpers.yaml_to_asdf(yaml) with asdf.open(buff) as af: assert af["quantity"].value == value assert af["quantity"].unit.is_equivalent(units.kpc) def test_read_array_value(): yaml = """ quantity: !unit/quantity-1.1.0 value: !core/ndarray-1.0.0 [1.0, 2.0, 3.0, 4.0] unit: km """ buff = helpers.yaml_to_asdf(yaml) with asdf.open(buff) as af: assert_array_equal(af["quantity"].value, np.array([1.0, 2.0, 3.0, 4.0])) assert af["quantity"].unit.is_equivalent(units.km) def test_memmap(tmp_path): """ Test that memmap (copy_arrays=False) works with quantities. Unfortunately, this is not a simple `isinstance(obj, np.memmap)` Instead it requires a more complicated check. """ file_path = tmp_path / "test.asdf" quantity = Quantity(np.arange(100, dtype=np.float64).reshape(5, 20), units.km) new_value = 42.14 * units.cm new_quantity = quantity.copy() new_quantity[-1, -1] = new_value # Write initial ASDF file with asdf.AsdfFile() as af: af.tree = {"quantity": quantity} af.write_to(file_path) # Update a value in the ASDF file with asdf.open(file_path, mode="rw", copy_arrays=False) as af: assert (af.tree["quantity"] == quantity).all() assert af.tree["quantity"][-1, -1] != new_value af.tree["quantity"][-1, -1] = new_value assert af.tree["quantity"][-1, -1] == new_value assert (af.tree["quantity"] != quantity).any() assert (af.tree["quantity"] == new_quantity).all() with asdf.open(file_path, mode="rw", copy_arrays=False) as af: assert af.tree["quantity"][-1, -1] == new_value assert (af.tree["quantity"] != quantity).any() assert (af.tree["quantity"] == new_quantity).all() def test_no_memmap(tmp_path): """ Test that turning off memmap (copy_arrays=True) works as expected for quantities """ file_path = tmp_path / "test.asdf" quantity = Quantity(np.arange(100, dtype=np.float64).reshape(5, 20), units.km) new_value = 42.14 * units.cm new_quantity = quantity.copy() new_quantity[-1, -1] = new_value # Write initial ASDF file with asdf.AsdfFile() as af: af.tree = {"quantity": quantity} af.write_to(file_path) # Update a value in the ASDF file with asdf.open(file_path, mode="rw", copy_arrays=True) as af: assert (af.tree["quantity"] == quantity).all() assert af.tree["quantity"][-1, -1] != new_value af.tree["quantity"][-1, -1] = new_value assert af.tree["quantity"][-1, -1] == new_value assert (af.tree["quantity"] != quantity).any() assert (af.tree["quantity"] == new_quantity).all() with asdf.open(file_path, mode="rw", copy_arrays=True) as af: assert af.tree["quantity"][-1, -1] != new_value assert (af.tree["quantity"] != new_quantity).any() assert (af.tree["quantity"] == quantity).all() asdf-astropy-0.5.0/asdf_astropy/converters/unit/tests/test_unit.py000066400000000000000000000037561452515624200255520ustar00rootroot00000000000000import warnings import asdf import pytest from asdf.testing import helpers from astropy import units def vounit_compatible(unit): with warnings.catch_warnings(): warnings.simplefilter("ignore", category=units.UnitsWarning) try: unit.to_string(format="vounit") except Exception: # noqa: BLE001 return False return True def create_vounits(): return {u for u in list(units.__dict__.values()) if isinstance(u, units.UnitBase) and vounit_compatible(u)} def create_non_vounits(): return {u for u in list(units.__dict__.values()) if isinstance(u, units.UnitBase) and not vounit_compatible(u)} @pytest.mark.parametrize("unit", create_vounits()) # Ignore warnings due to VOUnit deprecations @pytest.mark.filterwarnings("ignore::astropy.units.core.UnitsWarning") def test_vounit_serialization(unit, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["unit"] = unit af.write_to(file_path) with asdf.open(file_path) as af: assert af["unit"].is_equivalent(unit) with asdf.open(file_path, _force_raw_types=True) as af: assert isinstance(af["unit"], asdf.tagged.TaggedString) assert af["unit"]._tag.startswith("tag:stsci.edu:asdf/unit/unit-") @pytest.mark.parametrize("unit", create_non_vounits()) def test_non_vounit_serialization(unit, tmp_path): file_path = tmp_path / "test.asdf" with asdf.AsdfFile() as af: af["unit"] = unit af.write_to(file_path) with asdf.open(file_path) as af: assert af["unit"].is_equivalent(unit) with asdf.open(file_path, _force_raw_types=True) as af: assert isinstance(af["unit"], asdf.tagged.TaggedString) assert af["unit"]._tag.startswith("tag:astropy.org:astropy/units/unit-") def test_read(): yaml = """ unit: !unit/unit-1.0.0 "2.1798721 10-18kg m2 s-2" """ buff = helpers.yaml_to_asdf(yaml) with asdf.open(buff) as af: assert af["unit"].is_equivalent(units.Ry) asdf-astropy-0.5.0/asdf_astropy/converters/unit/unit.py000066400000000000000000000027061452515624200233430ustar00rootroot00000000000000import warnings from asdf.extension import Converter class UnitConverter(Converter): tags = ( "tag:stsci.edu:asdf/unit/unit-*", "tag:astropy.org:astropy/units/unit-*", ) types = ( "astropy.units.core.CompositeUnit", "astropy.units.core.IrreducibleUnit", "astropy.units.core.NamedUnit", "astropy.units.core.PrefixUnit", "astropy.units.core.Unit", "astropy.units.core.UnitBase", "astropy.units.core.UnrecognizedUnit", "astropy.units.function.mixin.IrreducibleFunctionUnit", "astropy.units.function.mixin.RegularFunctionUnit", ) def select_tag(self, obj, tags, ctx): from astropy.units import UnitsError, UnitsWarning with warnings.catch_warnings(): warnings.simplefilter("ignore", category=UnitsWarning) try: obj.to_string(format="vounit") except (UnitsError, ValueError): return next(t for t in tags if "astropy.org" in t) return next(t for t in tags if "stsci.edu" in t) def to_yaml_tree(self, obj, tag, ctx): if "stsci.edu" in tag: return obj.to_string(format="vounit") return obj.to_string() def from_yaml_tree(self, node, tag, ctx): from astropy.units import Unit kwargs = {"parse_strict": "silent"} if "stsci.edu" in tag: kwargs["format"] = "vounit" return Unit(node, **kwargs) asdf-astropy-0.5.0/asdf_astropy/converters/utils.py000066400000000000000000000007131452515624200225410ustar00rootroot00000000000000import importlib def import_type(type_name): """ Import a Python type from its fully-qualified name. For example, when this method is called with 'builtins.str' it will return the `str` type. Parameters ---------- type_name : str Type name, with module. Returns ------- type """ module_name, class_name = type_name.rsplit(".", 1) return getattr(importlib.import_module(module_name), class_name) asdf-astropy-0.5.0/asdf_astropy/extensions.py000066400000000000000000000507331452515624200214150ustar00rootroot00000000000000""" This module builds all of the ASDF extensions which will be registered by `asdf_astropy.integration`, via an ``entry-point`` in the ``pyproject.toml`` file. """ from asdf.extension import ManifestExtension from astropy.utils import minversion from ._manifest import CompoundManifestExtension from .converters.coordinates.angle import AngleConverter, LatitudeConverter, LongitudeConverter from .converters.coordinates.earth_location import EarthLocationConverter from .converters.coordinates.frame import FrameConverter, LegacyICRSConverter from .converters.coordinates.representation import RepresentationConverter from .converters.coordinates.sky_coord import SkyCoordConverter from .converters.coordinates.spectral_coord import SpectralCoordConverter from .converters.fits.fits import AsdfFitsConverter, AstropyFitsConverter from .converters.table.table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .converters.time.time import TimeConverter from .converters.time.time_delta import TimeDeltaConverter from .converters.transform.compound import CompoundConverter from .converters.transform.core import SimpleTransformConverter from .converters.transform.functional_models import ConstantConverter from .converters.transform.mappings import IdentityConverter, RemapAxesConverter, UnitsMappingConverter from .converters.transform.math_functions import MathFunctionsConverter from .converters.transform.polynomial import OrthoPolynomialConverter, PolynomialConverter from .converters.transform.projections import ProjectionConverter from .converters.transform.properties import CompoundBoundingBoxConverter, ModelBoundingBoxConverter from .converters.transform.rotations import Rotate3DConverter, RotationSequenceConverter from .converters.transform.spline import SplineConverter from .converters.transform.tabular import TabularConverter from .converters.unit.equivalency import EquivalencyConverter from .converters.unit.magunit import MagUnitConverter from .converters.unit.quantity import QuantityConverter from .converters.unit.unit import UnitConverter __all__ = [ "TRANSFORM_CONVERTERS", "TRANSFORM_MANIFEST_URIS", "TRANSFORM_EXTENSIONS", "COORDINATES_CONVERTERS", "ASTROPY_CONVERTERS", "COORDINATES_EXTENSION", "ASTROPY_EXTENSIONS", "CORE_CONVERTERS", "CORE_MANIFEST_URIS", "CORE_EXTENSIONS", ] TRANSFORM_CONVERTERS = [ # astropy.modeling.core CompoundConverter(), # astropy.modeling.functional_models SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/airy_disk2d-*"], "astropy.modeling.functional_models.AiryDisk2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/box1d-*"], "astropy.modeling.functional_models.Box1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/box2d-*"], "astropy.modeling.functional_models.Box2D", ), ConstantConverter(), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/disk2d-*"], "astropy.modeling.functional_models.Disk2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/ellipse2d-*"], "astropy.modeling.functional_models.Ellipse2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/exponential1d-*"], "astropy.modeling.functional_models.Exponential1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/gaussian1d-*"], "astropy.modeling.functional_models.Gaussian1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/gaussian2d-*"], "astropy.modeling.functional_models.Gaussian2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/king_projected_analytic1d-*"], "astropy.modeling.functional_models.KingProjectedAnalytic1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/linear1d-*"], "astropy.modeling.functional_models.Linear1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/logarithmic1d-*"], "astropy.modeling.functional_models.Logarithmic1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/lorentz1d-*"], "astropy.modeling.functional_models.Lorentz1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/moffat1d-*"], "astropy.modeling.functional_models.Moffat1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/moffat2d-*"], "astropy.modeling.functional_models.Moffat2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/multiplyscale-*"], "astropy.modeling.functional_models.Multiply", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/planar2d-*"], "astropy.modeling.functional_models.Planar2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/redshift_scale_factor-*"], "astropy.modeling.functional_models.RedshiftScaleFactor", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/ricker_wavelet1d-*"], "astropy.modeling.functional_models.RickerWavelet1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/ricker_wavelet2d-*"], "astropy.modeling.functional_models.RickerWavelet2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/ring2d-*"], "astropy.modeling.functional_models.Ring2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/scale-*"], "astropy.modeling.functional_models.Scale", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/sersic1d-*"], "astropy.modeling.functional_models.Sersic1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/sersic2d-*"], "astropy.modeling.functional_models.Sersic2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/shift-*"], "astropy.modeling.functional_models.Shift", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/sine1d-*"], "astropy.modeling.functional_models.Sine1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/cosine1d-*"], "astropy.modeling.functional_models.Cosine1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/tangent1d-*"], "astropy.modeling.functional_models.Tangent1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/arcsine1d-*"], "astropy.modeling.functional_models.ArcSine1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/arccosine1d-*"], "astropy.modeling.functional_models.ArcCosine1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/arctangent1d-*"], "astropy.modeling.functional_models.ArcTangent1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/trapezoid1d-*"], "astropy.modeling.functional_models.Trapezoid1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/trapezoid_disk2d-*"], "astropy.modeling.functional_models.TrapezoidDisk2D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/voigt1d-*"], "astropy.modeling.functional_models.Voigt1D", ), # astropy.modeling.mappings IdentityConverter(), RemapAxesConverter(), # UnitsMapping is not represented here because # it is an astropy-specific transform and not # included in the ASDF transform extension. # astropy.modeling.math_functions MathFunctionsConverter(), # astropy.modeling.physical_models SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/blackbody-*"], "astropy.modeling.physical_models.BlackBody", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/drude1d-*"], "astropy.modeling.physical_models.Drude1D", ), # TODO: Implement NFW SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/plummer1d-*"], "astropy.modeling.physical_models.Plummer1D", ), # astropy.modeling.polynomial PolynomialConverter(), OrthoPolynomialConverter(), # SIP and InverseSIP are deliberately excluded because they # are FITS-specific and can be easily represented by a # simple combination of existing models. SplineConverter(), # astropy.modeling.powerlaws SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/broken_power_law1d-*"], "astropy.modeling.powerlaws.BrokenPowerLaw1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/exponential_cutoff_power_law1d-*"], "astropy.modeling.powerlaws.ExponentialCutoffPowerLaw1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/log_parabola1d-*"], "astropy.modeling.powerlaws.LogParabola1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/power_law1d-*"], "astropy.modeling.powerlaws.PowerLaw1D", ), SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/smoothly_broken_power_law1d-*"], "astropy.modeling.powerlaws.SmoothlyBrokenPowerLaw1D", ), # astropy.modeling.projections SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/affine-*"], "astropy.modeling.projections.AffineTransformation2D", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/airy-*"], "astropy.modeling.projections.Pix2Sky_Airy", "astropy.modeling.projections.Sky2Pix_Airy", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/bonne_equal_area-*"], "astropy.modeling.projections.Pix2Sky_BonneEqualArea", "astropy.modeling.projections.Sky2Pix_BonneEqualArea", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/cobe_quad_spherical_cube-*"], "astropy.modeling.projections.Pix2Sky_COBEQuadSphericalCube", "astropy.modeling.projections.Sky2Pix_COBEQuadSphericalCube", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/conic_equal_area-*"], "astropy.modeling.projections.Pix2Sky_ConicEqualArea", "astropy.modeling.projections.Sky2Pix_ConicEqualArea", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/conic_equidistant-*"], "astropy.modeling.projections.Pix2Sky_ConicEquidistant", "astropy.modeling.projections.Sky2Pix_ConicEquidistant", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/conic_orthomorphic-*"], "astropy.modeling.projections.Pix2Sky_ConicOrthomorphic", "astropy.modeling.projections.Sky2Pix_ConicOrthomorphic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/conic_perspective-*"], "astropy.modeling.projections.Pix2Sky_ConicPerspective", "astropy.modeling.projections.Sky2Pix_ConicPerspective", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/cylindrical_equal_area-*"], "astropy.modeling.projections.Pix2Sky_CylindricalEqualArea", "astropy.modeling.projections.Sky2Pix_CylindricalEqualArea", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/cylindrical_perspective-*"], "astropy.modeling.projections.Pix2Sky_CylindricalPerspective", "astropy.modeling.projections.Sky2Pix_CylindricalPerspective", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/gnomonic-*"], "astropy.modeling.projections.Pix2Sky_Gnomonic", "astropy.modeling.projections.Sky2Pix_Gnomonic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/hammer_aitoff-*"], "astropy.modeling.projections.Pix2Sky_HammerAitoff", "astropy.modeling.projections.Sky2Pix_HammerAitoff", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/healpix-*"], "astropy.modeling.projections.Pix2Sky_HEALPix", "astropy.modeling.projections.Sky2Pix_HEALPix", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/healpix_polar-*"], "astropy.modeling.projections.Pix2Sky_HEALPixPolar", "astropy.modeling.projections.Sky2Pix_HEALPixPolar", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/mercator-*"], "astropy.modeling.projections.Pix2Sky_Mercator", "astropy.modeling.projections.Sky2Pix_Mercator", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/molleweide-*"], "astropy.modeling.projections.Pix2Sky_Molleweide", "astropy.modeling.projections.Sky2Pix_Molleweide", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/parabolic-*"], "astropy.modeling.projections.Pix2Sky_Parabolic", "astropy.modeling.projections.Sky2Pix_Parabolic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/plate_carree-*"], "astropy.modeling.projections.Pix2Sky_PlateCarree", "astropy.modeling.projections.Sky2Pix_PlateCarree", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/polyconic-*"], "astropy.modeling.projections.Pix2Sky_Polyconic", "astropy.modeling.projections.Sky2Pix_Polyconic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/quad_spherical_cube-*"], "astropy.modeling.projections.Pix2Sky_QuadSphericalCube", "astropy.modeling.projections.Sky2Pix_QuadSphericalCube", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/sanson_flamsteed-*"], "astropy.modeling.projections.Pix2Sky_SansonFlamsteed", "astropy.modeling.projections.Sky2Pix_SansonFlamsteed", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/slant_orthographic-*"], "astropy.modeling.projections.Pix2Sky_SlantOrthographic", "astropy.modeling.projections.Sky2Pix_SlantOrthographic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/slant_zenithal_perspective-*"], "astropy.modeling.projections.Pix2Sky_SlantZenithalPerspective", "astropy.modeling.projections.Sky2Pix_SlantZenithalPerspective", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/stereographic-*"], "astropy.modeling.projections.Pix2Sky_Stereographic", "astropy.modeling.projections.Sky2Pix_Stereographic", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/tangential_spherical_cube-*"], "astropy.modeling.projections.Pix2Sky_TangentialSphericalCube", "astropy.modeling.projections.Sky2Pix_TangentialSphericalCube", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/zenithal_equal_area-*"], "astropy.modeling.projections.Pix2Sky_ZenithalEqualArea", "astropy.modeling.projections.Sky2Pix_ZenithalEqualArea", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/zenithal_equidistant-*"], "astropy.modeling.projections.Pix2Sky_ZenithalEquidistant", "astropy.modeling.projections.Sky2Pix_ZenithalEquidistant", ), ProjectionConverter( ["tag:stsci.edu:asdf/transform/zenithal_perspective-*"], "astropy.modeling.projections.Pix2Sky_ZenithalPerspective", "astropy.modeling.projections.Sky2Pix_ZenithalPerspective", ), # astropy.modeling.rotations SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/rotate2d-*"], "astropy.modeling.rotations.Rotation2D", ), Rotate3DConverter(), RotationSequenceConverter(), # astropy.modeling.tabular TabularConverter(), # astropy.modeling.bounding_box ModelBoundingBoxConverter(), CompoundBoundingBoxConverter(), ] if minversion("astropy", "5.1.0"): TRANSFORM_CONVERTERS.append( SimpleTransformConverter( ["tag:stsci.edu:asdf/transform/schechter1d-*"], "astropy.modeling.powerlaws.Schechter1D", ), ) # The order here is important; asdf will prefer to use extensions # that occur earlier in the list. TRANSFORM_MANIFEST_URIS = [ "asdf://asdf-format.org/transform/manifests/transform-1.5.0", "asdf://asdf-format.org/transform/manifests/transform-1.4.0", "asdf://asdf-format.org/transform/manifests/transform-1.3.0", "asdf://asdf-format.org/transform/manifests/transform-1.2.0", "asdf://asdf-format.org/transform/manifests/transform-1.1.0", "asdf://asdf-format.org/transform/manifests/transform-1.0.0", ] TRANSFORM_EXTENSIONS = [ ManifestExtension.from_uri( uri, # This prevents a warning about a missing extension when opening # files written by older versions of the asdf library: legacy_class_names=["astropy.io.misc.asdf.extension.AstropyAsdfExtension"], converters=TRANSFORM_CONVERTERS, ) for uri in TRANSFORM_MANIFEST_URIS ] COORDINATES_CONVERTERS = [ FrameConverter( "tag:astropy.org:astropy/coordinates/frames/baseframe-*", "astropy.coordinates.baseframe.BaseCoordinateFrame", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/cirs-*", "astropy.coordinates.builtin_frames.cirs.CIRS", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/fk4-*", "astropy.coordinates.builtin_frames.fk4.FK4", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/fk4noeterms-*", "astropy.coordinates.builtin_frames.fk4.FK4NoETerms", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/fk5-*", "astropy.coordinates.builtin_frames.fk5.FK5", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/galactic-*", "astropy.coordinates.builtin_frames.galactic.Galactic", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/galactocentric-*", "astropy.coordinates.builtin_frames.galactocentric.Galactocentric", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/gcrs-*", "astropy.coordinates.builtin_frames.gcrs.GCRS", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/icrs-1.1.0", "astropy.coordinates.builtin_frames.icrs.ICRS", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/itrs-*", "astropy.coordinates.builtin_frames.itrs.ITRS", ), FrameConverter( "tag:astropy.org:astropy/coordinates/frames/precessedgeocentric-*", "astropy.coordinates.builtin_frames.gcrs.PrecessedGeocentric", ), LegacyICRSConverter(), AngleConverter(), LatitudeConverter(), LongitudeConverter(), EarthLocationConverter(), RepresentationConverter(), SkyCoordConverter(), SpectralCoordConverter(), ] ASTROPY_CONVERTERS = [ UnitsMappingConverter(), TimeDeltaConverter(), AstropyTableConverter(), AstropyFitsConverter(), NdarrayMixinConverter(), ] COORDINATES_EXTENSION = ManifestExtension.from_uri( "asdf://asdf-format.org/astronomy/coordinates/manifests/coordinates-1.0.0", converters=COORDINATES_CONVERTERS, ) _ASTROPY_EXTENSION_MANIFEST_URIS = [ "asdf://astropy.org/astropy/manifests/astropy-1.1.0", "asdf://astropy.org/astropy/manifests/astropy-1.0.0", ] ASTROPY_EXTENSIONS = [ ManifestExtension.from_uri( manifest_uri, # This prevents a warning about a missing extension when opening # files written by older versions of astropy: legacy_class_names=["astropy.io.misc.asdf.extension.AstropyExtension"], converters=ASTROPY_CONVERTERS, ) for manifest_uri in _ASTROPY_EXTENSION_MANIFEST_URIS ] # These tags are part of the ASDF Standard, # but we want to override serialization here so that users can # work with nice astropy objects for those entities. CORE_CONVERTERS = [ QuantityConverter(), TimeConverter(), ColumnConverter(), AsdfTableConverter(), AsdfFitsConverter(), ] UNIT_CONVETERS = [ UnitConverter(), EquivalencyConverter(), MagUnitConverter(), ] CORE_MANIFEST_URIS = [ "asdf://asdf-format.org/core/manifests/core-1.0.0", "asdf://asdf-format.org/core/manifests/core-1.1.0", "asdf://asdf-format.org/core/manifests/core-1.2.0", "asdf://asdf-format.org/core/manifests/core-1.3.0", "asdf://asdf-format.org/core/manifests/core-1.4.0", "asdf://asdf-format.org/core/manifests/core-1.5.0", "asdf://asdf-format.org/core/manifests/core-1.6.0", ] CORE_EXTENSIONS = [ CompoundManifestExtension( [ ManifestExtension.from_uri(u, converters=CORE_CONVERTERS), ManifestExtension.from_uri("asdf://astropy.org/astropy/manifests/units-1.0.0", converters=UNIT_CONVETERS), ], ) for u in CORE_MANIFEST_URIS ] asdf-astropy-0.5.0/asdf_astropy/integration.py000066400000000000000000000021761452515624200215370ustar00rootroot00000000000000import importlib.resources as importlib_resources from asdf.resource import DirectoryResourceMapping def get_resource_mappings(): """ Get the resource mapping instances for the astropy schemas and manifests. This method is registered with the asdf.resource_mappings entry point. Returns ------- list of collections.abc.Mapping """ from . import resources resources_root = importlib_resources.files(resources) return [ DirectoryResourceMapping(resources_root / "schemas", "http://astropy.org/schemas/astropy/", recursive=True), DirectoryResourceMapping(resources_root / "manifests", "asdf://astropy.org/astropy/manifests/"), ] def get_extensions(): """ Get the extension instances for the various astropy extensions. This method is registered with the asdf.extensions entry point. Returns ------- list of asdf.extension.Extension """ from . import extensions return [ *extensions.ASTROPY_EXTENSIONS, extensions.COORDINATES_EXTENSION, *extensions.TRANSFORM_EXTENSIONS, *extensions.CORE_EXTENSIONS, ] asdf-astropy-0.5.0/asdf_astropy/io/000077500000000000000000000000001452515624200172435ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/io/__init__.py000066400000000000000000000000001452515624200213420ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/io/connect.py000066400000000000000000000067521452515624200212600ustar00rootroot00000000000000# This file connects ASDF to the astropy.table.Table class import contextlib import asdf from astropy.io import registry as io_registry from astropy.table import Table def read_table(filename, data_key=None, find_table=None, **kwargs): """ Read a `~astropy.table.Table` object from an ASDF file By default, this function will look for a Table object with the key of ``data`` in the top-level ASDF tree. The parameters ``data_key`` and ``find_key`` can be used to override the default behavior. This function is registered as the Table reader for ASDF files with the unified I/O interface. Parameters ---------- filename : str or :class:`py.path:local` Name of the file to be read data_key : str Optional top-level key to use for finding the Table in the tree. If not provided, uses ``data`` by default. Use of this parameter is not compatible with ``find_table``. find_table : function Optional function to be used for locating the Table in the tree. The function takes a single parameter, which is a dictionary representing the top of the ASDF tree. The function must return a `~astropy.table.Table` instance. Returns ------- table : `~astropy.table.Table` `~astropy.table.Table` instance """ if data_key and find_table: msg = "Options 'data_key' and 'find_table' are not compatible" raise ValueError(msg) with asdf.open(filename, **kwargs) as af: if find_table: return find_table(af.tree) return af[data_key or "data"] def write_table(table, filename, data_key=None, make_tree=None, **kwargs): """ Write a `~astropy.table.Table` object to an ASDF file. By default, this function will write a Table object in the top-level ASDF tree using the key of ``data``. The parameters ``data_key`` and ``make_tree`` can be used to override the default behavior. This function is registered as the Table writer for ASDF files with the unified I/O interface. Parameters ---------- table : `~astropy.table.Table` `~astropy.table.Table` instance to be written filename : str or :class:`py.path:local` Name of the new ASDF file to be created data_key : str Optional top-level key in the ASDF tree to use when writing the Table. If not provided, uses ``data`` by default. Use of this parameter is not compatible with ``make_tree``. make_tree : function Optional function to be used for creating the ASDF tree. The function takes a single parameter, which is the `~astropy.table.Table` instance to be written. The function must return a `dict` representing the ASDF tree to be created. """ if data_key and make_tree: msg = "Options 'data_key' and 'make_tree' are not compatible" raise ValueError(msg) tree = make_tree(table) if make_tree else {data_key or "data": table} with asdf.AsdfFile(tree) as af: af.write_to(filename, **kwargs) def asdf_identify(origin, filepath, fileobj, *args, **kwargs): return filepath is not None and filepath.endswith(".asdf") # This means we're using an older version of astropy # and it has already claimed the 'asdf' identifier. with contextlib.suppress(io_registry.IORegistryError): io_registry.register_reader("asdf", Table, read_table) io_registry.register_writer("asdf", Table, write_table) io_registry.register_identifier("asdf", Table, asdf_identify) asdf-astropy-0.5.0/asdf_astropy/io/tests/000077500000000000000000000000001452515624200204055ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/io/tests/__init__.py000066400000000000000000000000001452515624200225040ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/io/tests/test_io.py000066400000000000000000000050351452515624200224300ustar00rootroot00000000000000import unittest.mock as mk import asdf import pytest from astropy.table import Table from asdf_astropy.io.connect import read_table, write_table def make_table(): a = [1, 4, 5] b = [2.0, 5.0, 8.2] c = ["x", "y", "z"] return Table([a, b, c], names=("a", "b", "c"), meta={"name": "first table"}) def test_table_io(tmp_path): tmp_file = tmp_path / "table.asdf" table = make_table() table.write(tmp_file) # Simple sanity check using ASDF directly with asdf.open(tmp_file) as af: assert "data" in af assert isinstance(af["data"], Table) assert all(af["data"] == table) # Now test using the table reader new_t = Table.read(tmp_file) assert all(new_t == table) def test_table_io_custom_key(tmp_path): tmp_file = tmp_path / "table.asdf" table = make_table() table.write(tmp_file, data_key="something") # Simple sanity check using ASDF directly with asdf.open(tmp_file) as af: assert "something" in af assert "data" not in af assert isinstance(af["something"], Table) assert all(af["something"] == table) # Now test using the table reader with pytest.raises(KeyError): new_t = Table.read(tmp_file) new_t = Table.read(tmp_file, data_key="something") assert all(new_t == table) def test_table_io_custom_tree(tmp_path): tmp_file = tmp_path / "table.asdf" table = make_table() def make_custom_tree(tab): return {"foo": {"bar": tab}} table.write(tmp_file, make_tree=make_custom_tree) # Simple sanity check using ASDF directly with asdf.open(tmp_file) as af: assert "foo" in af assert "bar" in af["foo"] assert "data" not in af assert all(af["foo"]["bar"] == table) # Now test using table reader with pytest.raises(KeyError): new_t = Table.read(tmp_file) def find_table(asdffile): return asdffile["foo"]["bar"] new_t = Table.read(tmp_file, find_table=find_table) assert all(new_t == table) def test_read_table_error(tmp_path): file_name = tmp_path / "table.asdf" with pytest.raises(ValueError, match="Options 'data_key' and 'find_table' are not compatible"): read_table(file_name, data_key=mk.MagicMock(), find_table=mk.MagicMock()) def test_write_table_error(tmp_path): file_name = tmp_path / "table.asdf" with pytest.raises(ValueError, match="Options 'data_key' and 'make_tree' are not compatible"): write_table(mk.MagicMock(), file_name, data_key=mk.MagicMock(), make_tree=mk.MagicMock()) asdf-astropy-0.5.0/asdf_astropy/resources/000077500000000000000000000000001452515624200206465ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/__init__.py000066400000000000000000000000001452515624200227450ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/manifests/000077500000000000000000000000001452515624200226375ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/manifests/astropy-1.0.0.yaml000066400000000000000000000045351452515624200256650ustar00rootroot00000000000000id: asdf://astropy.org/astropy/manifests/astropy-1.0.0 extension_uri: asdf://astropy.org/astropy/extensions/astropy-1.0.0 title: Astropy extension 1.0.0 description: |- A set of tags for serializing astropy objects. This does not include most model classes, which are handled by an implementation of the ASDF transform extension. asdf_standard_requirement: gte: 1.1.0 tags: - tag_uri: tag:astropy.org:astropy/time/timedelta-1.0.0 schema_uri: http://astropy.org/schemas/astropy/time/timedelta-1.0.0 title: Represents an instance of TimeDelta from astropy description: |- Represents the time difference between two times. - tag_uri: tag:astropy.org:astropy/fits/fits-1.0.0 schema_uri: http://astropy.org/schemas/astropy/fits/fits-1.0.0 title: A FITS file inside of an ASDF file. description: |- This schema is useful for distributing ASDF files that can automatically be converted to FITS files by specifying the exact content of the resulting FITS file. Not all kinds of data in FITS are directly representable in ASDF. For example, applying an offset and scale to the data using the `BZERO` and `BSCALE` keywords. In these cases, it will not be possible to store the data in the native format from FITS and also be accessible in its proper form in the ASDF file. Only image and binary table extensions are supported. - tag_uri: tag:astropy.org:astropy/table/table-1.0.0 schema_uri: http://astropy.org/schemas/astropy/table/table-1.0.0 title: A table. description: |- A table is represented as a list of columns, where each entry is a [column](ref:http://stsci.edu/schemas/asdf/core/column-1.0.0) object, containing the data and some additional information. The data itself may be stored inline as text, or in binary in either row- or column-major order by use of the `strides` property on the individual column arrays. Each column in the table must have the same first (slowest moving) dimension. - tag_uri: tag:astropy.org:astropy/transform/units_mapping-1.0.0 schema_uri: http://astropy.org/schemas/astropy/transform/units_mapping-1.0.0 title: Mapper that operates on the units of the input. description: |- This transform operates on the units of the input, first converting to the expected input units, then assigning replacement output units without further conversion. asdf-astropy-0.5.0/asdf_astropy/resources/manifests/astropy-1.1.0.yaml000066400000000000000000000051121452515624200256560ustar00rootroot00000000000000id: asdf://astropy.org/astropy/manifests/astropy-1.1.0 extension_uri: asdf://astropy.org/astropy/extensions/astropy-1.1.0 title: Astropy extension 1.1.0 description: |- A set of tags for serializing astropy objects. This does not include most model classes, which are handled by an implementation of the ASDF transform extension. asdf_standard_requirement: gte: 1.1.0 tags: - tag_uri: tag:astropy.org:astropy/time/timedelta-1.0.0 schema_uri: http://astropy.org/schemas/astropy/time/timedelta-1.0.0 title: Represents an instance of TimeDelta from astropy description: |- Represents the time difference between two times. - tag_uri: tag:astropy.org:astropy/fits/fits-1.0.0 schema_uri: http://astropy.org/schemas/astropy/fits/fits-1.0.0 title: A FITS file inside of an ASDF file. description: |- This schema is useful for distributing ASDF files that can automatically be converted to FITS files by specifying the exact content of the resulting FITS file. Not all kinds of data in FITS are directly representable in ASDF. For example, applying an offset and scale to the data using the `BZERO` and `BSCALE` keywords. In these cases, it will not be possible to store the data in the native format from FITS and also be accessible in its proper form in the ASDF file. Only image and binary table extensions are supported. - tag_uri: tag:astropy.org:astropy/table/table-1.1.0 schema_uri: http://astropy.org/schemas/astropy/table/table-1.1.0 title: A table. description: |- A table is represented as a list of columns, where each entry is a [column](ref:http://stsci.edu/schemas/asdf/core/column-1.0.0) object, containing the data and some additional information. The data itself may be stored inline as text, or in binary in either row- or column-major order by use of the `strides` property on the individual column arrays. Each column in the table must have the same first (slowest moving) dimension. - tag_uri: tag:astropy.org:astropy/transform/units_mapping-1.0.0 schema_uri: http://astropy.org/schemas/astropy/transform/units_mapping-1.0.0 title: Mapper that operates on the units of the input. description: |- This transform operates on the units of the input, first converting to the expected input units, then assigning replacement output units without further conversion. - tag_uri: tag:astropy.org:astropy/table/ndarraymixin-1.0.0 schema_uri: http://astropy.org/schemas/astropy/table/ndarraymixin-1.0.0 title: NdarrayMixin column. description: |- Represents an astropy.table.NdarrayMixin instance. asdf-astropy-0.5.0/asdf_astropy/resources/manifests/units-1.0.0.yaml000066400000000000000000000020661452515624200253230ustar00rootroot00000000000000 id: asdf://astropy.org/astropy/manifests/units-1.0.0 extension_uri: asdf://astropy.org/astropy/extensions/units-1.0.0 title: Astropy unit extension 1.0.0 description: |- A set of tags to inject into asdf-standard to enable serializing astropy units related objects tags: - tag_uri: tag:astropy.org:astropy/units/unit-1.0.0 schema_uri: http://stsci.edu/schemas/asdf/unit/unit-1.0.0 title: Represents an astropy derived unit description: |- Supports serialization of the non-VOunits supported by astropy - tag_uri: tag:astropy.org:astropy/units/equivalency-1.0.0 schema_uri: http://astropy.org/schemas/astropy/units/equivalency-1.0.0 title: Represents unit equivalency. description: |- Supports serialization of equivalencies between units in certain contexts - tag_uri: tag:astropy.org:astropy/units/magunit-1.0.0 schema_uri: http://astropy.org/schemas/astropy/units/magunit-1.0.0 title: Represents a Magnitude Unit description: |- Represents the serialization of the MagUnit units built into astropy. asdf-astropy-0.5.0/asdf_astropy/resources/schemas/000077500000000000000000000000001452515624200222715ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/fits/000077500000000000000000000000001452515624200232365ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/fits/fits-1.0.0.yaml000066400000000000000000000067431452515624200255330ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/fits/fits-1.0.0" title: > A FITS file inside of an ASDF file. description: | This schema is useful for distributing ASDF files that can automatically be converted to FITS files by specifying the exact content of the resulting FITS file. Not all kinds of data in FITS are directly representable in ASDF. For example, applying an offset and scale to the data using the `BZERO` and `BSCALE` keywords. In these cases, it will not be possible to store the data in the native format from FITS and also be accessible in its proper form in the ASDF file. Only image and binary table extensions are supported. examples: - - A simple FITS file with a primary header and two extensions - | ! - header: - [SIMPLE, true, conforms to FITS standard] - [BITPIX, 8, array data type] - [NAXIS, 0, number of array dimensions] - [EXTEND, true] - [] - ['', Top Level MIRI Metadata] - [] - [DATE, '2013-08-30T10:49:55.070373', The date this file was created (UTC)] - [FILENAME, MiriDarkReferenceModel_test.fits, The name of the file] - [TELESCOP, JWST, The telescope used to acquire the data] - [] - ['', Information about the observation] - [] - [DATE-OBS, '2013-08-30T10:49:55.000000', The date the observation was made (UTC)] - data: !core/ndarray-1.0.0 datatype: float32 shape: [2, 3, 3, 4] source: 0 byteorder: big header: - [XTENSION, IMAGE, Image extension] - [BITPIX, -32, array data type] - [NAXIS, 4, number of array dimensions] - [NAXIS1, 4] - [NAXIS2, 3] - [NAXIS3, 3] - [NAXIS4, 2] - [PCOUNT, 0, number of parameters] - [GCOUNT, 1, number of groups] - [EXTNAME, SCI, extension name] - [BUNIT, DN, Units of the data array] - data: !core/ndarray-1.0.0 datatype: float32 shape: [2, 3, 3, 4] source: 1 byteorder: big header: - [XTENSION, IMAGE, Image extension] - [BITPIX, -32, array data type] - [NAXIS, 4, number of array dimensions] - [NAXIS1, 4] - [NAXIS2, 3] - [NAXIS3, 3] - [NAXIS4, 2] - [PCOUNT, 0, number of parameters] - [GCOUNT, 1, number of groups] - [EXTNAME, ERR, extension name] - [BUNIT, DN, Units of the error array] allOf: - tag: "tag:astropy.org:astropy/fits/fits-1.0.0" - type: array items: type: object properties: # TODO: Why are there no validations for the header here? The # next version of the schema should add them. data: description: "The data part of the HDU." anyOf: - $ref: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0" - $ref: "../table/table-1.0.0" # Retain backwards compatibility with table defined by ASDF Standard - $ref: "http://stsci.edu/schemas/asdf/core/table-1.0.0" - type: "null" default: null asdf-astropy-0.5.0/asdf_astropy/resources/schemas/table/000077500000000000000000000000001452515624200233605ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/table/ndarraymixin-1.0.0.yaml000066400000000000000000000005641452515624200274100ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/table/ndarraymixin-1.0.0" title: > title: NdarrayMixin column. description: | Represents an astropy.table.NdarrayMixin instance. type: object properties: array: tag: "tag:stsci.edu:asdf/core/ndarray-*" additionalProperties: false required: [array] asdf-astropy-0.5.0/asdf_astropy/resources/schemas/table/table-1.0.0.yaml000066400000000000000000000072261452515624200257740ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/table/table-1.0.0" title: > A table. description: | A table is represented as a list of columns, where each entry is a [column](https://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/core/column-1.0.0.html) object, containing the data and some additional information. The data itself may be stored inline as text, or in binary in either row- or column-major order by use of the `strides` property on the individual column arrays. Each column in the table must have the same first (slowest moving) dimension. examples: - - A table stored in column-major order, with each column in a separate block - | ! columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] description: RA meta: {foo: bar} name: a unit: !unit/unit-1.0.0 deg - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 1 datatype: float64 byteorder: little shape: [3] description: DEC name: b - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 2 datatype: [ascii, 1] byteorder: big shape: [3] description: The target name name: c colnames: [a, b, c] - - A table stored in row-major order, all stored in the same block - | ! columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] strides: [13] description: RA meta: {foo: bar} name: a unit: !unit/unit-1.0.0 deg - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] offset: 4 strides: [13] description: DEC name: b - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: [ascii, 1] byteorder: big shape: [3] offset: 12 strides: [13] description: The target name name: c colnames: [a, b, c] type: object properties: columns: description: | A list of columns in the table. type: array items: anyOf: - $ref: "http://stsci.edu/schemas/asdf/core/column-1.0.0" - $ref: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0" - $ref: "http://stsci.edu/schemas/asdf/time/time-1.1.0" - $ref: "http://stsci.edu/schemas/asdf/unit/quantity-1.1.0" - $ref: "../coordinates/skycoord-1.0.0" - $ref: "../coordinates/earthlocation-1.0.0" - $ref: "../time/timedelta-1.0.0" colnames: description: | A list containing the names of the columns in the table (in order). type: array items: - type: string qtable: description: | A flag indicating whether or not the serialized type was a QTable type: boolean default: False meta: description: | Additional free-form metadata about the table. type: object default: {} additionalProperties: false required: [columns, colnames] asdf-astropy-0.5.0/asdf_astropy/resources/schemas/table/table-1.1.0.yaml000066400000000000000000000072731452515624200257770ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/table/table-1.1.0" title: > A table. description: | A table is represented as a list of columns, where each entry is a [column](https://asdf-standard.readthedocs.io/en/latest/generated/stsci.edu/asdf/core/column-1.0.0.html) object, containing the data and some additional information. The data itself may be stored inline as text, or in binary in either row- or column-major order by use of the `strides` property on the individual column arrays. Each column in the table must have the same first (slowest moving) dimension. examples: - - A table stored in column-major order, with each column in a separate block - | ! columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] description: RA meta: {foo: bar} name: a unit: !unit/unit-1.0.0 deg - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 1 datatype: float64 byteorder: little shape: [3] description: DEC name: b - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 2 datatype: [ascii, 1] byteorder: big shape: [3] description: The target name name: c colnames: [a, b, c] - - A table stored in row-major order, all stored in the same block - | ! columns: - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] strides: [13] description: RA meta: {foo: bar} name: a unit: !unit/unit-1.0.0 deg - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: float64 byteorder: little shape: [3] offset: 4 strides: [13] description: DEC name: b - !core/column-1.0.0 data: !core/ndarray-1.0.0 source: 0 datatype: [ascii, 1] byteorder: big shape: [3] offset: 12 strides: [13] description: The target name name: c colnames: [a, b, c] type: object properties: columns: description: | A list of columns in the table. type: array items: anyOf: - $ref: "http://stsci.edu/schemas/asdf/core/column-1.0.0" - $ref: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0" - $ref: "http://stsci.edu/schemas/asdf/time/time-1.1.0" - $ref: "http://stsci.edu/schemas/asdf/unit/quantity-1.1.0" - $ref: "../coordinates/skycoord-1.0.0" - $ref: "../coordinates/earthlocation-1.0.0" - $ref: "../time/timedelta-1.0.0" - $ref: "ndarraymixin-1.0.0" colnames: description: | A list containing the names of the columns in the table (in order). type: array items: - type: string qtable: description: | A flag indicating whether or not the serialized type was a QTable type: boolean default: False meta: description: | Additional free-form metadata about the table. type: object default: {} additionalProperties: false required: [columns, colnames] asdf-astropy-0.5.0/asdf_astropy/resources/schemas/time/000077500000000000000000000000001452515624200232275ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/time/timedelta-1.0.0.yaml000066400000000000000000000016731452515624200265240ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" id: "http://astropy.org/schemas/astropy/time/timedelta-1.0.0" title: Represents an instance of TimeDelta from astropy description: | Represents the time difference between two times. type: object properties: jd1: anyOf: - type: number - $ref: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0" description: | Value representing first 64 bits of precision jd2: anyOf: - type: number - $ref: "http://stsci.edu/schemas/asdf/core/ndarray-1.0.0" description: | Value representing second 64 bits of precision format: type: string description: | Format of time value representation. scale: type: string description: | Time scale of input value(s). enum: [tdb, tt, ut1, tcg, tcb, tai, local] required: [jd1, jd2, format] additionalProperties: False ... asdf-astropy-0.5.0/asdf_astropy/resources/schemas/transform/000077500000000000000000000000001452515624200243045ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/transform/units_mapping-1.0.0.yaml000066400000000000000000000052041452515624200305000ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/transform/units_mapping-1.0.0" title: | Mapper that operates on the units of the input. description: | This transform operates on the units of the input, first converting to the expected input units, then assigning replacement output units without further conversion. examples: - - Assign units of seconds to dimensionless input. - | ! unit_inputs: - name: x unit: !unit/unit-1.0.0 unit_outputs: - name: x unit: !unit/unit-1.0.0 s - - Convert input to meters, then assign dimensionless units. - | ! unit_inputs: - name: x unit: !unit/unit-1.0.0 m unit_outputs: - name: x unit: !unit/unit-1.0.0 - - Convert input to meters, then drop units entirely. - | ! unit_inputs: - name: x unit: !unit/unit-1.0.0 m unit_outputs: - name: x - - Accept any units, then replace with meters. - | ! unit_inputs: - name: x unit_outputs: - name: x unit: !unit/unit-1.0.0 m allOf: - $ref: "http://stsci.edu/schemas/asdf/transform/transform-1.2.0" - type: object properties: unit_inputs: description: | Array of input configurations. type: array items: $ref: "#/definitions/value_configuration" unit_outputs: description: | Array of output configurations. type: array items: $ref: "#/definitions/value_configuration" required: [unit_inputs, unit_outputs] definitions: value_configuration: description: | Configuration of a single model value (input or output). type: object properties: name: description: | Value name. type: string unit: description: | Expected unit. $ref: "http://stsci.edu/schemas/asdf/unit/unit-1.0.0" equivalencies: description: | Equivalencies to apply when converting value to expected unit. $ref: "http://astropy.org/schemas/astropy/units/equivalency-1.0.0" allow_dimensionless: description: | Allow this value to receive dimensionless data. type: boolean default: false required: [name] ... asdf-astropy-0.5.0/asdf_astropy/resources/schemas/units/000077500000000000000000000000001452515624200234335ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/resources/schemas/units/equivalency-1.0.0.yaml000066400000000000000000000012771452515624200273050ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/units/equivalency-1.0.0" title: | Represents unit equivalency. description: | Supports serialization of equivalencies between units in certain contexts definitions: equivalency: type: object properties: name: type: string kwargs_names: type: array items: type: string kwargs_values: type: array items: anyOf: - $ref: "http://stsci.edu/schemas/asdf/unit/quantity-1.1.0" - type: number - type: "null" type: array items: $ref: "#/definitions/equivalency" ... asdf-astropy-0.5.0/asdf_astropy/resources/schemas/units/magunit-1.0.0.yaml000066400000000000000000000006351452515624200264210ustar00rootroot00000000000000%YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" id: "http://astropy.org/schemas/astropy/units/magunit-1.0.0" title: | Represents a Magnitude Unit description: | Represents the serialization of the MagUnit units built into astropy. type: object properties: unit: oneOf: - tag: "tag:stsci.edu:asdf/unit/unit-1.0.0" - tag: "tag:astropy.org:astropy/units/unit-1.0.0" asdf-astropy-0.5.0/asdf_astropy/testing/000077500000000000000000000000001452515624200203115ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/testing/__init__.py000066400000000000000000000000001452515624200224100ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/testing/helpers.py000066400000000000000000000141401452515624200223250ustar00rootroot00000000000000""" Helpers for testing astropy objects in ASDF files. """ import numpy as np def assert_earth_location_equal(a, b): """ Assert earth location objects are equal. """ __tracebackhide__ = True assert (a == b).all() def assert_representation_equal(a, b): """ Assert representation objects are equal. """ from astropy import units __tracebackhide__ = True assert type(a) is type(b) assert a.components == b.components for component in a.components: assert units.allclose(getattr(a, component), getattr(b, component)) def assert_sky_coord_equal(a, b): """ Assert sky coordinate objects are equal. """ __tracebackhide__ = True assert a.is_equivalent_frame(b) assert a.representation_type is b.representation_type assert a.shape == b.shape assert_representation_equal(a.data, b.data) def assert_frame_equal(a, b): """ Assert frame objects are equal. """ __tracebackhide__ = True assert type(a) is type(b) if a is None: return if a.has_data: assert b.has_data assert_representation_equal(a.data, b.data) else: return def assert_spectral_coord_equal(a, b): """ Assert spectral coordinate objects are equal. """ from astropy.tests.helper import assert_quantity_allclose __tracebackhide__ = True assert type(a) is type(b) assert_quantity_allclose(a.quantity, b.quantity) assert_frame_equal(a.observer, b.observer) assert_frame_equal(a.target, b.target) def assert_time_equal(a, b): """ Assert time objects are equal """ from astropy.coordinates import EarthLocation assert a.format == b.format assert a.scale == b.scale assert isinstance(a.location, type(b.location)) if isinstance(a.location, EarthLocation): assert_earth_location_equal(a.location, b.location) else: assert a.location == b.location if a.format == "plot_date": np.testing.assert_array_almost_equal(a.value, b.value) else: np.testing.assert_array_equal(a, b) def assert_time_delta_equal(a, b): """ Assert time delta objects are equal """ np.testing.assert_array_equal(a.jd, b.jd) np.testing.assert_array_equal(a.jd2, b.jd2) np.testing.assert_array_equal(a.sec, b.sec) def assert_hdu_list_equal(a, b): """ Assert fits hdulists are equal. """ assert len(a) == len(b) for hdu_a, hdu_b in zip(a, b): np.testing.assert_array_equal(hdu_a.data, hdu_b.data) assert len(hdu_a.header.cards) == len(hdu_b.header.cards) for card_a, card_b in zip(hdu_a.header.cards, hdu_b.header.cards): assert tuple(card_a) == tuple(card_b) def assert_description_equal(a, b): """ Assert table descriptions are equal. """ if a in ("", None) and b in ("", None): return assert a == b def assert_table_equal(a, b): """ Assert astropy tables are equal. """ from astropy.table import Column, MaskedColumn assert type(a) == type(b) assert a.meta == b.meta assert len(a) == len(b) for row_a, row_b in zip(a, b): np.testing.assert_array_equal(row_a, row_b) assert a.colnames == b.colnames for column_name in a.colnames: col_a = a[column_name] col_b = b[column_name] if isinstance(col_a, (Column, MaskedColumn)) and isinstance(col_b, (Column, MaskedColumn)): assert_description_equal(col_a.description, col_b.description) assert col_a.unit == col_b.unit assert col_a.meta == col_b.meta np.testing.assert_array_equal(col_a.data, col_b.data) np.testing.assert_array_equal( getattr(col_a, "mask", [False] * len(col_a)), getattr(col_b, "mask", [False] * len(col_b)), ) def assert_table_roundtrip(table, tmp_path): """ Assert that a table can be written to an ASDF file and read back in without losing any of its essential properties. """ import asdf file_path = tmp_path / "testable.asdf" with asdf.AsdfFile({"table": table}) as af: af.write_to(file_path) with asdf.open(file_path) as af: assert_table_equal(table, af["table"]) return af["table"] def assert_model_equal(a, b): """ Assert that two model instances are equivalent. """ if a is None and b is None: return assert a.__class__ == b.__class__ assert a.name == b.name assert a.inputs == b.inputs assert a.input_units == b.input_units assert a.outputs == b.outputs assert a.input_units_allow_dimensionless == b.input_units_allow_dimensionless assert a.input_units_equivalencies == b.input_units_equivalencies np.testing.assert_array_equal(a.parameters, b.parameters) assert a._user_bounding_box == b._user_bounding_box try: a_bounding_box = a.bounding_box except NotImplementedError: a_bounding_box = None try: b_bounding_box = b.bounding_box except NotImplementedError: b_bounding_box = None assert a_bounding_box == b_bounding_box assert a.fixed == b.fixed assert a.bounds == b.bounds assert_model_equal(a._user_inverse, b._user_inverse) def assert_bounding_box_roundtrip(bounding_box, tmp_path, version=None): """ Assert that a bounding_box can be written to an ASDF file and read back in without losing any of its essential properties. """ import asdf path = str(tmp_path / "test.asdf") with asdf.AsdfFile({"bounding_box": bounding_box}, version=version) as af: af.write_to(path) with asdf.open(path) as af: assert bounding_box == af["bounding_box"](bounding_box._model) def assert_model_roundtrip(model, tmp_path, version=None): """ Assert that a model can be written to an ASDF file and read back in without losing any of its essential properties. """ import asdf path = str(tmp_path / "test.asdf") with asdf.AsdfFile({"model": model}, version=version) as af: af.write_to(path) with asdf.open(path) as af: assert_model_equal(model, af["model"]) return af["model"] asdf-astropy-0.5.0/asdf_astropy/tests/000077500000000000000000000000001452515624200177765ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/tests/__init__.py000066400000000000000000000000001452515624200220750ustar00rootroot00000000000000asdf-astropy-0.5.0/asdf_astropy/tests/test_integration.py000066400000000000000000000117731452515624200237430ustar00rootroot00000000000000import io import sys from pathlib import Path import asdf import pytest import yaml def test_resources(): resources_root = Path(__file__).parent.parent / "resources" resource_manager = asdf.get_config().resource_manager for resource_path in resources_root.glob("**/*.yaml"): with resource_path.open("rb") as f: resource_content = f.read() resource = yaml.safe_load(resource_content) resource_uri = resource["id"] assert resource_manager[resource_uri] == resource_content def test_manifests(): manifests_root = Path(__file__).parent.parent / "resources" / "manifests" resource_manager = asdf.get_config().resource_manager for manifest_path in manifests_root.glob("*.yaml"): with manifest_path.open("rb") as f: manifest_content = f.read() manifest = yaml.safe_load(manifest_content) manifest_schema = asdf.schema.load_schema("asdf://asdf-format.org/core/schemas/extension_manifest-1.0.0") # The manifest must be valid against its own schema: asdf.schema.validate(manifest, schema=manifest_schema) for tag_definition in manifest["tags"]: # The tag's schema must be available: assert tag_definition["schema_uri"] in resource_manager def test_extensions(): package_and_uri_pairs = {(e.package_name, e.extension_uri) for e in asdf.get_config().extensions} assert ("asdf-astropy", "asdf://astropy.org/astropy/extensions/astropy-1.0.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.0.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.1.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.2.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.3.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.4.0") in package_and_uri_pairs assert ("asdf-astropy", "asdf://asdf-format.org/transform/extensions/transform-1.5.0") in package_and_uri_pairs _ASTROPY_MODULES = [ "astropy.coordinates", "astropy.io", "astropy.modeling", "astropy.table", "astropy.time", "astropy.units", ] @pytest.fixture() def _clean_astropy_imports(): """Temporally unload all astropy modules used by asdf-astropy""" # If any astropy or asdf_astropy modules are already imported # remove them from sys.modules so we can later in this test check # if those modules were imported. We have to store these as # many submodules will reference or use types defined in other # submodules previous_modules = {} for name in sys.modules.copy(): if name.startswith("asdf_astropy") or any(name.startswith(m) for m in _ASTROPY_MODULES): previous_modules[name] = sys.modules[name] del sys.modules[name] # Register a module finder that just raises an exception if # one of the tracked astropy modules is imported class _Finder: def find_spec(self, modulename, path=None, target=None): if any(modulename.startswith(m) for m in _ASTROPY_MODULES): msg = f"attempt to import astropy submodule({modulename}) during integration" raise Exception(msg) # noqa: TRY002 sys.meta_path.insert(0, _Finder()) # Setup complete return the test yield # Restore the previously imported modules. This is necessary as other code # may have already used the modules we removed from the cache (astropy.constants # defines some quantities). for name in previous_modules: sys.modules[name] = previous_modules[name] if isinstance(sys.meta_path[0], _Finder): sys.meta_path.pop(0) @pytest.mark.usefixtures("_clean_astropy_imports") def test_no_astropy_import(): """ Confirm that none of the ASDF plugins import astropy modules at import time. """ from asdf_astropy import integration integration.get_resource_mappings() integration.get_extensions() assert not any(k for k in sys.modules if any(k.startswith(m) for m in _ASTROPY_MODULES)) def test_no_core_extension_overwrite(): """ Check that this package (even though it implements converters for core types) does not overwrite the core extension provided by asdf """ import astropy.units as u # define a tree with a unit that will be serialized by this package # the tree itself will be serialized by asdf (which should result in # including asdf as a used extension) tree = {"meter": u.m} bio = io.BytesIO() asdf.AsdfFile(tree).write_to(bio) bio.seek(0) with asdf.open(bio) as af: packages = [ext["software"]["name"] for ext in af.tree["history"]["extensions"]] # check that both asdf and asdf-astropy are recorded as being used to # generate the file assert "asdf" in packages assert "asdf-astropy" in packages asdf-astropy-0.5.0/docs/000077500000000000000000000000001452515624200150665ustar00rootroot00000000000000asdf-astropy-0.5.0/docs/Makefile000066400000000000000000000107451452515624200165350ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest #This is needed with git because git doesn't create a dir if it's empty $(shell [ -d "_static" ] || mkdir -p _static) help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf $(BUILDDIR) -rm -rf api -rm -rf generated html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: @echo "Run 'python setup.py test' in the root directory to run doctests " \ @echo "in the documentation." asdf-astropy-0.5.0/docs/asdf-astropy/000077500000000000000000000000001452515624200175025ustar00rootroot00000000000000asdf-astropy-0.5.0/docs/asdf-astropy/api.rst000066400000000000000000000005151452515624200210060ustar00rootroot00000000000000.. _api: === API === The classes and functions here describe the API for asdf-astropy. .. automodapi:: asdf_astropy.testing.helpers .. automodapi:: asdf_astropy.converters :inherited-members: .. automodapi:: asdf_astropy.extensions :include-all-objects: :inherited-members: .. automodapi:: asdf_astropy.integration asdf-astropy-0.5.0/docs/asdf-astropy/details.rst000066400000000000000000000043711452515624200216660ustar00rootroot00000000000000.. _details: ======= Details ======= **ASDF** makes use of an abstract data type definition called a **tag**, which is a formed from a **schema** or collection of **schemas**. Each **schema** encodes part of the information that **ASDF** uses to both validate and identify the organization and types of data within an ASDF file. Tags are assigned to specific a specific schema or collection of schemas within a **manifest**. Finally, **ASDF** requires **converter** classes which implement the logic of serializing and deserializing of objects (in this case **astropy** classes) into and out of their respective **ASDF** tag representations. The **asdf-astropy** package primarily defines **converters** for many **astropy** types, and then properly registers them with **ASDF**. Users should never need to refer to converter implementations directly. Their presence should be entirely transparent when processing ASDF files. The converters in **asdf-astropy** related to transforms implement the tags that are defined by the :ref:`asdf-transform-schemas package `. Similarly, the converters in **asdf-astropy** related to coordinates implement the tags that are defined by the :ref:`asdf-coordinates-schemas package `. Moreover, many of the converters in **asdf-astropy** related to units implement tags that are defined in the :ref:`ASDF-standard `. Finally, there are converters in **asdf-astropy** whose tags are defined within **asdf-astropy** itself. See :ref:`asdf-astropy_manifest` for a listing of all these tags. Documentation of the individual schemas defined by **asdf-astropy**, which are used to assemble these tags can be found in :ref:`asdf-astropy_schemas`. .. note:: Not all **astropy** types are currently serializable by ASDF. Attempting to write unsupported types to an ASDF file will lead to a ``RepresenterError``. In order to support new types, new tags and converters must be created. A basic example can be found in :ref:`basic_example`, for additional details please refer to :ref:`asdf:extending_extensions`. If you do write additional converters or schemas please consider contributing them to **asdf-astropy**. asdf-astropy-0.5.0/docs/asdf-astropy/example.rst000066400000000000000000000160641452515624200216760ustar00rootroot00000000000000.. _basic_example: ======= Example ======= In this example, we will show how to implement serialization for a new `~astropy.modeling.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 :ref:`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 .. code-block:: yaml %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: .. code-block:: yaml - 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 :ref:`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 :ref:`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:"] 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 :ref:`asdf:extending_extensions`. asdf-astropy-0.5.0/docs/asdf-astropy/install.rst000066400000000000000000000030131452515624200216770ustar00rootroot00000000000000.. _installation: ************ Installation ************ There are several different ways to install the :ref:`asdf-astropy` package. Each is described in detail below. Requirements ============ The :ref:`asdf-astropy` package has several dependencies which are all listed in the project's ``setup.cfg`` file. All dependencies are available on pypi and will be automatically installed along with :ref:`asdf-astropy`. Most importantly, both the `asdf` and :ref:`astropy:getting-started` packages will be installed along with :ref:`asdf-astropy` which should enable full functionality. Installation with pip ===================== .. include:: ../../README.rst :start-after: begin-pip-install-text: :end-before: end-pip-install-text: Installing with conda ===================== :ref:`asdf-astropy` is also distributed as a `conda `__ package via the `conda-forge `__ channel. To install :ref:`asdf-astropy` within an existing conda environment .. code-block:: console $ conda install -c conda-forge asdf-astropy To create a new conda environment and install :ref:`asdf-astropy` .. code-block:: console $ conda create -n new-env-name -c conda-forge python asdf-astropy Building from source ==================== .. include:: ../../README.rst :start-after: begin-source-install-text: :end-before: end-source-install-text: Running the tests ================= .. include:: ../../README.rst :start-after: begin-testing-text: :end-before: end-testing-text: asdf-astropy-0.5.0/docs/asdf-astropy/manifest.rst000066400000000000000000000003741452515624200220460ustar00rootroot00000000000000.. _asdf-astropy_manifest: ========================= **asdf-astropy** Manifest ========================= Documentation of the ASDF manifest for **asdf-astropy** can be found below: .. asdf-schema:: :standard_prefix: manifests astropy-1.0.0 asdf-astropy-0.5.0/docs/asdf-astropy/migrating.rst000066400000000000000000000025631452515624200222230ustar00rootroot00000000000000.. _migrating: Migrating from **astropy.io.misc.asdf** to **asdf-astropy** =========================================================== For the majority of users, migrating from **astropy.io.misc.asdf** to **asdf-astropy** requires no code changes at all. Instead, all users need to do is include **asdf-astropy** as an additional dependency to their package. This is because when the **asdf-astropy** package is installed, it will automatically be used by **ASDF** when serializing and deserializing **astropy** objects. This occurs seamlessly because the interface used by **asdf-astropy** to extend **ASDF** is given preference over the one used by **astropy.io.misc.asdf** (which is currently deprecated). .. note:: When **ASDF** version 3.0 is released, the interface used by **astropy.io.misc.asdf** will be removed. This means that using **ASDF** with **astropy** will stop functioning unless **asdf-astropy** is installed. The only users of **astropy.io.misc.asdf** that need to do any code migration aside from adding an **asdf-astropy** dependency are those who directly use the objects (based on **asdf.types.CustomType**) defined in **astropy.io.misc.asdf** to create their own **ASDF** `~asdf.extension._converter.Converter` extensions. Clear instructions on how to create the new converter extensions for **ASDF** can be found in :ref:`asdf:extending_converters`. asdf-astropy-0.5.0/docs/asdf-astropy/quickstart.rst000066400000000000000000000053421452515624200224320ustar00rootroot00000000000000.. _quickstart: *********** Quick-Start *********** **asdf-astropy** is intended to be an extension library for **ASDF** to enable support for the **astropy** package. It is intended to be used in conjunction with both the **ASDF** and **astropy** packages. To this end, the **asdf-astropy** package typically only needs to be installed in order to provide its functionality. A quick example of **ASDF** =========================== The **ASDF** format is a way of saving nested structures to **yaml**, where some of the stored data can be stored in a binary format. Thus, one typically structures an **ASDF** file as a dictionary, with key/value pairs. For example if one wanted to store a `~astropy.modeling.functional_models.Gaussian1D` model in an **ASDF** file:: from asdf import AsdfFile from astropy.modeling.models import Gaussian1D # Create a Gaussian1D model model = Gaussian1D(amplitude=10.4, mean=3.2, stddev=0.1) # Create a tree structure for the ASDF file, and write it. tree = {'gaussian_model': model} ff = AsdfFile(tree) ff.write_to("hello_world.asdf") # One can also create the file first, and then modify it directly. ff = AsdfFile() ff.tree['gaussian_model'] = model ff.write_to("hello_world.asdf") To open the existing file one can use the top-level `asdf.open` method directly or as a context manager:: import asdf ff = asdf.open("hello_world.asdf") # As a context manager with asdf.open("hello_world.asdf") as ff: ... In either case the file ``ff`` will be an `asdf.AsdfFile` object which is accessible just like a Python dictionary. **ASDF** will fully realize all of the objects stored within the file automatically. For example, to access the model stored in the file ``ff['gaussian_model']`` will be a `~astropy.modeling.functional_models.Gaussian1D` object exactly matching the model written originally. One can also update an existing file. For example, if one wanted to update the ``hello_world.asdf`` file with a `~astropy.coordinates.SkyCoord` object:: import asdf from astropy import units as u from astropy.coordinates import SkyCoord # Create a SkyCoord object coord = SkyCoord(ra=10.625*u.degree, dec=41.2*u.degree, frame='icrs') with asdf.open("hello_world.asdf", mode='rw') as af: af.tree['skycoord'] = coord af.update() In the same way that the `~astropy.modeling.functional_models.Gaussian1D` model round-tripped, the `~astropy.coordinates.SkyCoord` object will also round-trip. Further Reading =============== For further getting started material on **ASDF** see, :ref:`asdf:overview`. If one wants to find more examples of how to write an **astropy** `~astropy.table.Table` to **ASDF** files, see :ref:`table`. asdf-astropy-0.5.0/docs/asdf-astropy/schemas.rst000066400000000000000000000025231452515624200216610ustar00rootroot00000000000000.. _asdf-astropy_schemas: ======================== **asdf-astropy** Schemas ======================== Documentation for each of the individual ASDF schemas defined by **asdf-astropy** can be found at the links below. Documentation for the schemas defined in the ASDF Standard can be found :ref:`here `. Note that other schemas are defined in :ref:`asdf-transform-schemas ` and :ref:`asdf-coordinates-schemas `. .. contents:: FITS ---- The following schemas are associated with **astropy** objects from the :ref:`astropy-io-fits` submodule: fits/fits-1.0.0 ^^^^^^^^^^^^^^^ .. asdf-autoschemas:: fits/fits-1.0.0 Table ----- The following schemas are associated with **astropy** objects from the :ref:`astropy-table` submodule: table/table-1.0.0 ^^^^^^^^^^^^^^^^^ .. asdf-autoschemas:: table/table-1.0.0 Time ---- The following schemas are associated with **astropy** objects from the :ref:`astropy-time` submodule: time/timedelta-1.0.0 ^^^^^^^^^^^^^^^^^^^^ .. asdf-autoschemas:: time/timedelta-1.0.0 Units ----- The following schemas are associated with **astropy** objects from the :ref:`astropy-units` submodule: units/equivalency-1.0.0 ^^^^^^^^^^^^^^^^^^^^^^^ .. asdf-autoschemas:: units/equivalency-1.0.0 asdf-astropy-0.5.0/docs/asdf-astropy/table.rst000066400000000000000000000073371452515624200213350ustar00rootroot00000000000000.. _table: ***************************** Using **ASDF** with Table I/O ***************************** **ASDF** provides readers and writers for `~astropy.table.Table` using the :ref:`table_io`. This makes it convenient to read and write **ASDF** files with `~astropy.table.Table` data. Basic Usage =========== Given a table, it is possible to write it out to an **ASDF** file:: from astropy.table import Table # Create a simple table t = Table(dtype=[('a', 'f4'), ('b', 'i4'), ('c', 'S2')]) # Write the table to an ASDF file t.write('table.asdf') The I/O registry automatically selects the appropriate writer function to use based on the ``.asdf`` extension of the output file. Reading a file generated in this way is also possible using `~astropy.table.Table.read`:: t2 = Table.read('table.asdf') The I/O registry automatically selects the appropriate reader function based on the extension of the input file. In the case of both reading and writing, if the file extension is not ``.asdf`` it is possible to explicitly specify the reader/writer function to be used:: t3 = Table.read('table.zxcv', format='asdf') Advanced Usage ^^^^^^^^^^^^^^ The fundamental **ASDF** data structure is the tree, which is a nested combination of basic data structures (see :ref:`asdf:data-model` for a more detailed description). At the top level, the tree is a `dict`. The consequence of this is that a `~astropy.table.Table` object (or any object, for that matter) can be stored at any arbitrary location within an **ASDF** tree. The basic writer use case described above stores the given `~astropy.table.Table` at the top of the tree using a default key. The basic reader case assumes that a `~astropy.table.Table` is stored in the same place. However, it may sometimes be useful for users to specify a different top-level key to be used for storage and retrieval of a `~astropy.table.Table` from an **ASDF** file. For this reason, the **ASDF** I/O interface provides ``data_key`` as an optional keyword when writing and reading:: from astropy.table import Table t = Table(dtype=[('a', 'f4'), ('b', 'i4'), ('c', 'S2')]) # Write the table to an asdf file using a non-default key t.write('foo.asdf', data_key='foo') A `~astropy.table.Table` stored using a custom data key can be retrieved by passing the same argument to `~astropy.table.Table.read`:: foo = Table.read('foo.asdf', data_key='foo') The ``data_key`` option only applies to `~astropy.table.Table` objects that are stored at the top of the **ASDF** tree. For full generality, users may pass a callback when writing or reading **ASDF** files to define precisely where the `~astropy.table.Table` object should be placed in the tree. The option for the write case is ``make_tree``. The function callback should accept exactly one argument, which is the `~astropy.table.Table` object, and should return a `dict` representing the tree to be stored:: def make_custom_tree(table): # Return a nested tree where the table is stored at the second level return dict(foo=dict(bar=table)) t = Table(dtype=[('a', 'f4'), ('b', 'i4'), ('c', 'S2')]) # Write the table to an **ASDF** file using a non-default key t.write('foobar.asdf', make_tree=make_custom_tree) Similarly, when reading an **ASDF** file, the user can pass a custom callback to locate the table within the **ASDF** tree. The option in this case is ``find_table``. The callback should accept exactly one argument, which is an `dict` representing the **ASDF** tree, and it should return a `~astropy.table.Table` object:: def find_table(tree): # This returns the Table that was stored by the example above return tree['foo']['bar'] foo = Table.read('foobar.asdf', find_table=find_table) asdf-astropy-0.5.0/docs/conf.py000066400000000000000000000202331452515624200163650ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this file. # # All configuration values have a default. Some values are defined in # the global Astropy configuration which is loaded here before anything else. # See astropy.sphinx.conf for which values are set there. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # IMPORTANT: the above commented section was generated by sphinx-quickstart, but # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left # commented out with this explanation to make it clear why this should not be # done. If the sys.path entry above is added, when the astropy.sphinx.conf # import occurs, it will import the *source* version of astropy instead of the # version installed (if invoked as "make html" or directly with sphinx), or the # version in the build directory (if "python setup.py build_sphinx" is used). # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. import datetime import sys from importlib import import_module from pathlib import Path import sphinx # noqa: F403, F401 import sphinx_astropy # noqa: F403, F401 import tomli try: from sphinx_astropy.conf.v1 import * # noqa: F403, F401 except ImportError: print("ERROR: the documentation requires the sphinx-astropy package to be installed") sys.exit(1) # Get configuration information from `pyproject.toml` with open(Path(__file__).parent.parent / "pyproject.toml", "rb") as configuration_file: conf = tomli.load(configuration_file) configuration = conf["project"] # -- General configuration ---------------------------------------------------- # By default, highlight as Python 3. highlight_language = "python3" # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = "1.7" intersphinx_mapping["asdf"] = ("https://asdf.readthedocs.io/en/latest/", None) intersphinx_mapping["asdf-standard"] = ("https://asdf-standard.readthedocs.io/en/latest/", None) intersphinx_mapping["asdf-transform-schemas"] = ("https://asdf-transform-schemas.readthedocs.io/en/latest/", None) intersphinx_mapping["asdf-coordinates-schemas"] = ("https://asdf-coordinates-schemas.readthedocs.io/en/latest/", None) # To perform a Sphinx version check that needs to be more specific than # major.minor, call `check_sphinx_version("x.y.z")` here. # check_sphinx_version("1.2.1") # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns.append("_templates") # This is added to the end of RST files - a good place to put substitutions to # be used globally. # # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does project = configuration["name"] author = f"{configuration['authors'][0]['name']} <{configuration['authors'][0]['email']}>" copyright = f"{datetime.datetime.now().year}, {configuration['authors'][0]}" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. module_name = project.replace("-", "_") import_module(module_name) package = sys.modules[module_name] # The short X.Y version. version = package.__version__.split("-", 1)[0] # The full version, including alpha/beta/rc tags. release = package.__version__ # -- Options for HTML output -------------------------------------------------- # A NOTE ON HTML THEMES # The global astropy configuration uses a custom theme, 'bootstrap-astropy', # which is installed along with astropy. A different theme can be used or # the options for this theme can be modified by overriding some of the # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. # html_theme_path = [] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. # html_theme = None html_theme_options = { "logotext1": "asdf-astropy", # white, semi-bold "logotext2": "", # orange, light "logotext3": ":docs", # white, light } # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = '' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = '' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = f"{project} v{release}" # Output file base name for HTML help builder. htmlhelp_basename = project + "doc" # -- Options for LaTeX output ------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [("index", project + ".tex", project + " Documentation", author, "manual")] # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", project.lower(), project + " Documentation", [author], 1)] extensions += ["sphinx_asdf"] # extensions = ['sphinx.ext.autodoc', # 'sphinx.ext.autosummary', # 'sphinx_automodapi.automodsumm', # 'sphinx_automodapi.autodoc_enhancements', # 'sphinx_automodapi.smart_resolver', # 'sphinx_automodapi.automodapi', # ] # -- Options for the edit_on_github extension --------------------------------- # if setup_cfg.get('edit_on_github').lower() == 'true': # # extensions += ['sphinx_astropy.ext.edit_on_github'] # # # edit_on_github_project = setup_cfg['github_project'] # edit_on_github_branch = "main" # # edit_on_github_source_root = "" # edit_on_github_doc_root = "docs" # # # -- Resolving issue number to links in changelog ----------------------------- # github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project']) # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- nitpicky = True # nitpick_ignore = [] # # Some warnings are impossible to suppress, and you can list specific references # that should be ignored in a nitpick-exceptions file which should be inside # the docs/ directory. The format of the file should be: # # # # for example: # # py:class astropy.io.votable.tree.Element # py:class astropy.io.votable.tree.SimpleElement # py:class astropy.io.votable.tree.SimpleElementWithContent # # Uncomment the following lines to enable the exceptions: # # for line in open('nitpick-exceptions'): # if line.strip() == "" or line.startswith("#"): # continue # dtype, target = line.split(None, 1) # target = target.strip() # nitpick_ignore.append((dtype, six.u(target))) # -- sphinx_asdf configuration --------------------------------------------- # Top-level directory containing ASDF schemas (relative to current directory) asdf_schema_path = "../asdf_astropy/resources" # This is the prefix common to all schema IDs in this repository asdf_schema_standard_prefix = "schemas" asdf-astropy-0.5.0/docs/index.rst000066400000000000000000000023731452515624200167340ustar00rootroot00000000000000.. _asdf-astropy: ************************************** The **asdf-astropy** Extension Package ************************************** The **asdf-astropy** package contains code that is used to serialize **astropy** objects so that they can be represented and stored using the Advanced Scientific Data Format (**ASDF**). If **asdf-astropy** is installed, no further configuration is required in order to process **ASDF** files that contain **astropy** objects. Note that the **ASDF** package has been designed to automatically detect the presence of tags defined by packages like **asdf-astropy** and automatically make use of that package's support infrastructure to operate correctly. Documentation on the **ASDF Standard** can be found :ref:`here `. Documentation on the **ASDF** Python library can be found :ref:`here `. Getting Started =============== .. toctree:: :maxdepth: 2 asdf-astropy/install.rst asdf-astropy/quickstart.rst asdf-astropy/migrating.rst Using **asdf-astropy** ====================== .. toctree:: :maxdepth: 2 asdf-astropy/table.rst asdf-astropy/details.rst asdf-astropy/example.rst asdf-astropy/manifest.rst asdf-astropy/schemas.rst asdf-astropy/api.rst asdf-astropy-0.5.0/docs/make.bat000066400000000000000000000107051452515624200164760ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* del /q /s api del /q /s generated goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end asdf-astropy-0.5.0/licenses/000077500000000000000000000000001452515624200157435ustar00rootroot00000000000000asdf-astropy-0.5.0/licenses/README.rst000066400000000000000000000005641452515624200174370ustar00rootroot00000000000000Licenses ======== This directory holds license and credit information for the package, works the package is derived from, and/or datasets. Ensure that you pick a package license which is in this folder and it matches the one mentioned in the top level README.rst file. If you are using the pre-rendered version of this template check for the word 'Other' in the README. asdf-astropy-0.5.0/pyproject.toml000066400000000000000000000075561452515624200170670ustar00rootroot00000000000000[project] name = "asdf-astropy" description = "ASDF serialization support for astropy" readme = 'README.rst' license = { file = 'LICENSE.rst' } authors = [{ name = 'The Astropy Developers', email = 'astropy.team@gmail.com' }] requires-python = '>=3.9' classifiers = [ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python', "Programming Language :: Python :: 3 :: Only", 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', "Programming Language :: Python :: 3.11", ] dynamic = [ 'version', ] dependencies = [ "asdf>=2.13", "asdf-coordinates-schemas>=0.1", "asdf-transform-schemas>=0.2.2", "astropy>=5.0.4", "numpy>=1.20", "packaging>=19", ] [project.optional-dependencies] docs = [ "docutils", "graphviz", "matplotlib", "sphinx", "sphinx-asdf", "sphinx-astropy", "sphinx-automodapi", "tomli", ] test = [ "coverage", "pytest-astropy", "scipy", ] [project.urls] 'documentation' = 'https://asdf-astropy.readthedocs.io/en/latest/' 'repository' = 'https://github.com/astropy/asdf-astropy' 'tracker' = 'https://github.com/astropy/asdf-astropy/issues' [project.entry-points] 'asdf.extensions' = {asdf-astropy = 'asdf_astropy.integration:get_extensions'} 'asdf.resource_mappings' = {asdf-astropy = 'asdf_astropy.integration:get_resource_mappings'} [build-system] build-backend = 'setuptools.build_meta' requires = [ "setuptools>=60", "setuptools_scm[toml]>=3.4", ] [tool.setuptools] packages = ["asdf_astropy", "asdf_astropy.resources"] [tool.setuptools.package-data] "asdf_astropy.resources" = ["asdf_astropy/resources/**/**/*.yaml"] [tool.setuptools_scm] write_to = "asdf_astropy/_version.py" [tool.black] line-length = 120 force-exclude = ''' ^/( ( \.eggs | \.git | \.pytest_cache | \.tox )/ ) ''' [tool.ruff] target-version = "py38" line-length = 120 select = ["ALL"] extend-ignore = [ # Ignore check groups "C90", # mccabe "D", # pydocstyle "ANN", # flake8-annotations "ARG", # flake8-unused-arguments "DTZ", # flake8-datetimez "TD", # flake8-todos "FIX", # flake8-fixme # Individually ignored checks "SLF001", # private-member-access ] extend-exclude = ["docs/*"] [tool.ruff.per-file-ignores] "test_*.py" = ["S101"] "asdf_astropy/testing/helpers.py" = ["S101"] "scripts/generate_manifest.py" = ["S101", "SLOT000"] [tool.isort] profile = "black" filter_files = true line_length = 120 [tool.pytest.ini_options] testpaths = ['asdf_astropy', 'docs'] astropy_header = true doctest_plus = 'enabled' text_file_format = 'rst' filterwarnings = [ 'error', 'ignore:numpy.ndarray size changed:RuntimeWarning', ] addopts = '--color=yes --doctest-rst' asdf_schema_root = 'asdf_astropy/resources/schemas' asdf_schema_tests_enabled = true [tool.coverage.run] omit = [ 'asdf_astropy/_astropy_init*', 'asdf_astropy/conftest.py', 'asdf_astropy/*setup_package*', 'asdf_astropy/tests/*', 'asdf_astropy/*/tests/*', 'asdf_astropy/extern/*', 'asdf_astropy/version*', '*/asdf_astropy/_astropy_init*', '*/asdf_astropy/conftest.py', '*/asdf_astropy/*setup_package*', '*/asdf_astropy/tests/*', '*/asdf_astropy/*/tests/*', '*/asdf_astropy/extern/*', '*/asdf_astropy/version*', ] [tool.coverage.report] exclude_lines = [ # Have to re-enable the standard pragma 'pragma: no cover', # Don't complain about packages we have installed 'except ImportError', # Don't complain if tests don't hit assertions 'raise AssertionError', 'raise NotImplementedError', # Don't complain about script hooks 'def main\(.*\):', # Ignore branches that don't pertain to this version of Python 'pragma: py{ignore_python_version}', # Don't complain about IPython completion helper 'def _ipython_key_completions_', ] [tool.codespell] skip="*.pdf,*.fits,*.asdf,*.egg-info,.tox,build,./tags,.git,./docs/_build" asdf-astropy-0.5.0/requirements-dev.txt000066400000000000000000000003371452515624200202010ustar00rootroot00000000000000git+https://github.com/astropy/astropy git+https://github.com/asdf-format/asdf git+https://github.com/asdf-format/asdf-standard.git git+https://github.com/asdf-format/asdf-transform-schemas scipy>=0.0.dev0 numpy>=0.0.dev0 asdf-astropy-0.5.0/scripts/000077500000000000000000000000001452515624200156255ustar00rootroot00000000000000asdf-astropy-0.5.0/scripts/__init__.py000066400000000000000000000000001452515624200177240ustar00rootroot00000000000000asdf-astropy-0.5.0/scripts/generate_manifest.py000066400000000000000000000036731452515624200216700ustar00rootroot00000000000000""" Script that creates initial astropy manifest from the schemas in the resources directory. This file can be removed once the format of the manifest files has been finalized. """ import argparse from pathlib import Path import yaml def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("schemas_path") parser.add_argument("output_path") return parser.parse_args() class MultilineString(str): pass def represent_multiline_string(dumper, data): return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") if __name__ == "__main__": yaml.add_representer(MultilineString, represent_multiline_string) args = parse_args() manifest = {} manifest["id"] = "http://astropy.org/asdf/extensions/astropy/manifests/astropy-1.0" manifest["extension_uri"] = "http://astropy.org/asdf/extensions/astropy-1.0" manifest["title"] = "Astropy extension 1.0" manifest["description"] = MultilineString( "A set of tags for serializing astropy objects. This does not include most\n" "model classes, which are handled by an implementation of the ASDF\n" "transform extension.", ) manifest["asdf_standard_requirement"] = { "gte": "1.1.0", } manifest["tags"] = [] for schema_path in Path(args.schemas_path).glob("**/*.yaml"): schema = yaml.safe_load(schema_path.read_bytes()) tag_def = { "tag_uri": schema["id"].replace("http://astropy.org/schemas/astropy/", "tag:astropy.org:astropy/"), "schema_uri": schema["id"], "title": schema["title"].strip(), } if "tag" in schema: assert tag_def["tag_uri"] == schema["tag"] if "description" in schema: tag_def["description"] = MultilineString(schema["description"].strip()) manifest["tags"].append(tag_def) with Path(args.output_path).open("w") as f: yaml.dump(manifest, f, sort_keys=False) asdf-astropy-0.5.0/tox.ini000066400000000000000000000143311452515624200154530ustar00rootroot00000000000000[tox] envlist = py{39,310}-test{,-alldeps} py38-test-devdeps py38-cov py{39,}-test-numpy{119,120,121,122} py38-astropylts py39-test-oldestdep-parallels-cov requires = setuptools >= 30.3.0 pip >= 19.3.1 isolated_build = true [testenv] description = run tests alldeps: with all optional dependencies devdeps: with the latest developer version of key dependencies cov: and test coverage numpy119: with numpy 1.19.* numpy120: with numpy 1.20.* numpy121: with numpy 1.21.* numpy122: with numpy 1.22.* numpy123: with numpy 1.23.* numpy124: with numpy 1.24.* astropylts: with astropy LTS setenv = devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple # The following provides some specific pinnings for key packages deps = cov: coverage numpy119: numpy==1.19.* numpy120: numpy==1.20.* numpy121: numpy==1.21.* numpy122: numpy==1.22.* numpy123: numpy==1.23.* numpy124: numpy==1.24.* astropylts: astropy==5.0.* transformlts: asdf-transform-schemas==0.2.* devdeps: -rrequirements-dev.txt oldestdeps: minimum_dependencies parallel: pytest-xdist extras = test alldeps: all commands_pre= devdeps: pip install -U --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy # Generate `requiremments-min.txt` oldestdeps: minimum_dependencies asdf-astropy --filename {envtmpdir}/requirements-min.txt # Force install everything from `requirements-min.txt` oldestdeps: pip install -r {envtmpdir}/requirements-min.txt pip freeze commands = pytest \ parallel: --numprocesses auto \ cov: --cov-report xml --cov asdf_astropy {posargs} [testenv:asdf] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/asdf-format/asdf.git pip install -e asdf[tests] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest asdf/asdf [testenv:asdf-standard] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/asdf-format/asdf-standard.git pip install -e asdf-standard[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest asdf-standard [testenv:asdf-transform-schemas] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/asdf-format/asdf-transform-schemas.git pip install -e asdf-transform-schemas[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest asdf-transform-schemas [testenv:asdf-coordinates-schemas] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/asdf-format/asdf-coordinates-schemas.git pip install -e asdf-coordinates-schemas[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest asdf-coordinates-schemas [testenv:specutils] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/astropy/specutils.git pip install -e specutils[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest specutils [testenv:gwcs] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/spacetelescope/gwcs.git pip install -e gwcs[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest gwcs [testenv:jwst] deps= pytest-xdist changedir={envtmpdir} allowlist_externals= git bash setenv= CRDS_SERVER_URL = https://jwst-crds.stsci.edu CRDS_PATH = tmp/crds_cache CRDS_CLIENT_RETRY_COUNT = 3 CRDS_CLIENT_RETRY_DELAY_SECONDS = 20 extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/spacetelescope/jwst.git pip install -e jwst[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest --numprocesses auto jwst [testenv:stdatamodels] changedir={envtmpdir} allowlist_externals= git bash setenv= CRDS_SERVER_URL = https://jwst-crds.stsci.edu CRDS_PATH = tmp/crds_cache CRDS_CLIENT_RETRY_COUNT = 3 CRDS_CLIENT_RETRY_DELAY_SECONDS = 20 extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/spacetelescope/stdatamodels.git pip install -e stdatamodels[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest stdatamodels [testenv:roman_datamodels] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/spacetelescope/roman_datamodels.git pip install -e roman_datamodels[test] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest roman_datamodels/tests [testenv:sunpy] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/sunpy/sunpy.git pip install -e sunpy[tests,all] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest sunpy/sunpy/io [testenv:dkist] changedir={envtmpdir} allowlist_externals= git bash extras= commands_pre= bash -c "pip freeze -q | grep 'asdf-astropy @' > {envtmpdir}/requirements.txt" git clone https://github.com/DKISTDC/dkist.git pip install -e dkist[tests] pip install -r {envtmpdir}/requirements.txt pip freeze commands= pytest dkist