Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ jobs:
- libopenjp2-7
- graphviz

type_check:
uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v3 # zizmor: ignore[unpinned-uses]
with:
submodules: false
pytest: false
toxdeps: tox-pypi-filter
envs: |
- linux: mypy
- linux: pyright

cron:
if: |
github.event_name == 'workflow_dispatch' || (
Expand All @@ -111,7 +121,7 @@ jobs:
github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'Run publish')
)
needs: [test, docs]
needs: [test, docs, type_check]
uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v2 # zizmor: ignore[unpinned-uses]
with:
python-version: '3.13'
Expand Down
1 change: 1 addition & 0 deletions changelog/950.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add type and type checking to NDCube
4 changes: 2 additions & 2 deletions docs/explaining_ndcube/slicing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ If we want our region of interest to only apply to a single sub-cube, and we ind
>>> single_cube_roi.array_axis_physical_types
[('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)]

However, as with numpy slicing, we can induce the slicing operation to return an `~ndcube.NDCubeSequence` by supplying a length-1 `slice` to the sequence axis, rather than an `int`.
However, as with numpy slicing, we can induce the slicing operation to return an `~ndcube.NDCubeSequence` by supplying a length-1 ``slice`` to the sequence axis, rather than an `int`.
This sequence will still represent the same region of interest from the same single sub-cube, but the sequence axis will have a length of 1, rather than be removed.

.. code-block:: python
Expand Down Expand Up @@ -309,7 +309,7 @@ This can be achieved by entering:
[('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)]

This returns the same `~ndcube.NDCube` as above.
However, also as above, we can induce the return type to be an `~ndcube.NDCubeSequence` by supplying a length-1 `slice`.
However, also as above, we can induce the return type to be an `~ndcube.NDCubeSequence` by supplying a length-1 ``slice``.
As before, the same region of interest from the same sub-cube is represented, just with sequence and common axes of length 1.

.. code-block:: python
Expand Down
9 changes: 9 additions & 0 deletions docs/nitpick-exceptions
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ py:class an object providing a view on D's values
py:class D[k] if k in D, else d. d defaults to None.
py:class None. Remove all items from D.
py:class None. Update D from mapping/iterable E and F.
py:class None. Update D from dict/iterable E and F.
py:class v, remove specified key and return the corresponding value.

# This comes from Map.wcs
Expand Down Expand Up @@ -77,6 +78,14 @@ py:class numpy.core.records.recarray
py:class numpy.ma.core.MaskedArray
py:class numpy.ma.mvoid
py:class numpy.void

# numpy.typing.NDArray's real __module__ is this private path, not the
# public numpy.typing name, so autodoc can't resolve it via intersphinx
py:class numpy._typing._array_like.NDArray

# ndcube.utils.cube.WCSType is an internal typing.TypeAlias, not a
# documented public class
py:class WCSType
py:class pandas.DataFrame
py:class xmlrpc.client.Error
py:class xmlrpc.client.Fault
Expand Down
12 changes: 7 additions & 5 deletions ndcube/asdf/converters/compoundwcs_converter.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from typing import Any

from asdf.extension import Converter


class CompoundConverter(Converter):
class CompoundConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/compoundwcs-*"]
types = ["ndcube.wcs.wrappers.compound_wcs.CompoundLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.wcs.wrappers import CompoundLowLevelWCS

return CompoundLowLevelWCS(*node["wcs"], mapping=node.get("mapping"), pixel_atol=node.get("atol"))
return CompoundLowLevelWCS(*node["wcs"], mapping=node.get("mapping"), pixel_atol=node.get("atol")) # type: ignore[arg-type]

def to_yaml_tree(self, compoundwcs, tag, ctx):
node = {}
def to_yaml_tree(self, compoundwcs: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["wcs"] = compoundwcs._wcs
node["mapping"] = compoundwcs.mapping.mapping
node["atol"] = compoundwcs.atol
Expand Down
38 changes: 20 additions & 18 deletions ndcube/asdf/converters/extracoords_converter.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
from typing import Any

from asdf.extension import Converter


class ExtraCoordsConverter(Converter):
class ExtraCoordsConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/extra_coords/extra_coords/extracoords-*"]
types = ["ndcube.extra_coords.extra_coords.ExtraCoords"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.extra_coords.extra_coords import ExtraCoords
extra_coords = ExtraCoords()
extra_coords._wcs = node.get("wcs")
extra_coords._mapping = node.get("mapping")
extra_coords._lookup_tables = node.get("lookup_tables", [])
extra_coords._dropped_tables = node.get("dropped_tables")
extra_coords._ndcube = node.get("ndcube")
extra_coords._wcs = node.get("wcs") # pyright: ignore[reportPrivateUsage]
extra_coords._mapping = node.get("mapping") # pyright: ignore[reportPrivateUsage]
extra_coords._lookup_tables = node.get("lookup_tables", []) # pyright: ignore[reportPrivateUsage]
extra_coords._dropped_tables = node.get("dropped_tables", []) # pyright: ignore[reportPrivateUsage]
extra_coords._ndcube = node.get("ndcube") # pyright: ignore[reportPrivateUsage]
return extra_coords

def to_yaml_tree(self, extracoords, tag, ctx):
node = {}
if extracoords._wcs is not None:
node["wcs"] = extracoords._wcs
if extracoords._mapping is not None:
node["mapping"] = extracoords._mapping
if extracoords._lookup_tables:
node["lookup_tables"] = extracoords._lookup_tables
if extracoords._dropped_tables is not None:
node["dropped_tables"] = extracoords._dropped_tables
node["ndcube"] = extracoords._ndcube
def to_yaml_tree(self, extracoords: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
if extracoords._wcs is not None: # pyright: ignore[reportPrivateUsage]
node["wcs"] = extracoords._wcs # pyright: ignore[reportPrivateUsage]
if extracoords._mapping is not None: # pyright: ignore[reportPrivateUsage]
node["mapping"] = extracoords._mapping # pyright: ignore[reportPrivateUsage]
if extracoords._lookup_tables: # pyright: ignore[reportPrivateUsage]
node["lookup_tables"] = extracoords._lookup_tables # pyright: ignore[reportPrivateUsage]
if extracoords._dropped_tables is not None: # pyright: ignore[reportPrivateUsage]
node["dropped_tables"] = extracoords._dropped_tables # pyright: ignore[reportPrivateUsage]
node["ndcube"] = extracoords._ndcube # pyright: ignore[reportPrivateUsage]
return node
20 changes: 11 additions & 9 deletions ndcube/asdf/converters/globalcoords_converter.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
from typing import Any

from asdf.extension import Converter


class GlobalCoordsConverter(Converter):
class GlobalCoordsConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/global_coords/globalcoords-*"]
types = ["ndcube.global_coords.GlobalCoords"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.global_coords import GlobalCoords

globalcoords = GlobalCoords()
if "internal_coords" in node:
globalcoords._internal_coords = node["internal_coords"]
globalcoords._ndcube = node["ndcube"]
globalcoords._internal_coords = node["internal_coords"] # pyright: ignore[reportPrivateUsage]
globalcoords._ndcube = node["ndcube"] # pyright: ignore[reportPrivateUsage]

return globalcoords

def to_yaml_tree(self, globalcoords, tag, ctx):
node = {}
node["ndcube"] = globalcoords._ndcube
if globalcoords._internal_coords:
node["internal_coords"] = globalcoords._internal_coords
def to_yaml_tree(self, globalcoords: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["ndcube"] = globalcoords._ndcube # pyright: ignore[reportPrivateUsage]
if globalcoords._internal_coords: # pyright: ignore[reportPrivateUsage]
node["internal_coords"] = globalcoords._internal_coords # pyright: ignore[reportPrivateUsage]

return node
14 changes: 8 additions & 6 deletions ndcube/asdf/converters/ndcollection_converter.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
from typing import Any

from asdf.extension import Converter


class NDCollectionConverter(Converter):
class NDCollectionConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/ndcollection-*"]
types = ["ndcube.ndcollection.NDCollection"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.ndcollection import NDCollection

aligned_axes = list(node.get("aligned_axes", {}).values())
aligned_axes = tuple(tuple(lst) for lst in aligned_axes)
aligned_axes_list = list(node.get("aligned_axes", {}).values())
aligned_axes = tuple(tuple(lst) for lst in aligned_axes_list)
return NDCollection(node["items"], meta=node.get("meta"), aligned_axes=aligned_axes)

def to_yaml_tree(self, ndcollection, tag, ctx):
node = {}
def to_yaml_tree(self, ndcollection: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["items"] = dict(ndcollection)
if ndcollection.meta is not None:
node["meta"] = ndcollection.meta
Expand Down
13 changes: 7 additions & 6 deletions ndcube/asdf/converters/ndcube_converter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import warnings
from typing import Any

from asdf.extension import Converter


class NDCubeConverter(Converter):
class NDCubeConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/ndcube-*"]
types = ["ndcube.ndcube.NDCube"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.ndcube import NDCube

ndcube = NDCube(
Expand All @@ -19,13 +20,13 @@ def from_yaml_tree(self, node, tag, ctx):
uncertainty=node.get("uncertainty"),
)
if "extra_coords" in node:
ndcube._extra_coords = node["extra_coords"]
ndcube._extra_coords = node["extra_coords"] # pyright: ignore[reportPrivateUsage]
if "global_coords" in node:
ndcube._global_coords = node["global_coords"]
ndcube._global_coords = node["global_coords"] # pyright: ignore[reportPrivateUsage]

return ndcube

def to_yaml_tree(self, ndcube, tag, ctx):
def to_yaml_tree(self, ndcube: Any, tag: str, ctx: Any) -> dict[str, Any]:
"""
Notes
-----
Expand All @@ -41,7 +42,7 @@ def to_yaml_tree(self, ndcube, tag, ctx):
This ensures that users are aware of potentially important information
that is not included in the serialized output.
"""
node = {}
node: dict[str, Any] = {}
node["data"] = ndcube.data
# NDData always has .wcs as a high level wcs
node["wcs"] = ndcube.wcs.low_level_wcs
Expand Down
10 changes: 6 additions & 4 deletions ndcube/asdf/converters/ndcubesequence_converter.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
from typing import Any

from asdf.extension import Converter


class NDCubeSequenceConverter(Converter):
class NDCubeSequenceConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/ndcubesequence-*"]
types = ["ndcube.ndcube_sequence.NDCubeSequence"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.ndcube_sequence import NDCubeSequence

return NDCubeSequence(node["data"],
meta=node.get("meta"),
common_axis=node.get("common_axis"))

def to_yaml_tree(self, ndcseq, tag, ctx):
node = {}
def to_yaml_tree(self, ndcseq: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["data"] = ndcseq.data
if ndcseq.meta is not None:
node["meta"] = ndcseq.meta
Expand Down
14 changes: 8 additions & 6 deletions ndcube/asdf/converters/ndmeta_converter.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
from typing import Any

import numpy as np

from asdf.extension import Converter


class NDMetaConverter(Converter):
class NDMetaConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/meta/ndmeta-*"]
types = ["ndcube.meta.NDMeta"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.meta import NDMeta
axes = {k: np.array(v) for k, v in node["axes"].items()}
meta = NDMeta(node["meta"], node["key_comments"], axes, node["data_shape"])
meta._original_meta = node["original_meta"]
meta._original_meta = node["original_meta"] # pyright: ignore[reportPrivateUsage]
return meta

def to_yaml_tree(self, meta, tag, ctx):
node = {}
def to_yaml_tree(self, meta: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["meta"] = dict(meta)
node["key_comments"] = meta.key_comments
node["axes"] = meta.axes
node["data_shape"] = meta.data_shape
node["original_meta"] = meta._original_meta # not the MappingProxy object
node["original_meta"] = meta._original_meta # not the MappingProxy object # pyright: ignore[reportPrivateUsage]
return node
10 changes: 6 additions & 4 deletions ndcube/asdf/converters/reorderedwcs_converter.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Any

from asdf.extension import Converter


class ReorderedConverter(Converter):
class ReorderedConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/reorderedwcs-*"]
types = ["ndcube.wcs.wrappers.reordered_wcs.ReorderedLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.wcs.wrappers import ReorderedLowLevelWCS

return ReorderedLowLevelWCS(
Expand All @@ -14,8 +16,8 @@ def from_yaml_tree(self, node, tag, ctx):
world_order=node["world_order"],
)

def to_yaml_tree(self, reorderedwcs, tag, ctx):
node = {}
def to_yaml_tree(self, reorderedwcs: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["wcs"] = reorderedwcs._wcs
node["pixel_order"] = reorderedwcs._pixel_order
node["world_order"] = reorderedwcs._world_order
Expand Down
10 changes: 6 additions & 4 deletions ndcube/asdf/converters/resampled_converter.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Any

from asdf.extension import Converter


class ResampledConverter(Converter):
class ResampledConverter(Converter): # type: ignore[misc]
tags = ["tag:sunpy.org:ndcube/resampledwcs-*"]
types = ["ndcube.wcs.wrappers.resampled_wcs.ResampledLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
def from_yaml_tree(self, node: dict[str, Any], tag: str, ctx: Any) -> Any:
from ndcube.wcs.wrappers import ResampledLowLevelWCS

return ResampledLowLevelWCS(
Expand All @@ -14,8 +16,8 @@ def from_yaml_tree(self, node, tag, ctx):
factor=node["factor"],
)

def to_yaml_tree(self, resampledwcs, tag, ctx):
node = {}
def to_yaml_tree(self, resampledwcs: Any, tag: str, ctx: Any) -> dict[str, Any]:
node: dict[str, Any] = {}
node["wcs"] = resampledwcs._wcs
node["factor"] = resampledwcs._factor
node["offset"] = resampledwcs._offset
Expand Down
Loading
Loading