Coverage for python / lsst / images / serialization / _tables.py: 68%
61 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 01:57 -0800
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 01:57 -0800
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = (
15 "ColumnDefinitionModel",
16 "TableCellReferenceModel",
17 "TableModel",
18 "UnsupportedTableError",
19)
21import operator
22from typing import TYPE_CHECKING, ClassVar, Literal
24import astropy.units
25import numpy as np
26import numpy.typing as npt
27import pydantic
29from ._asdf_utils import Unit
30from ._common import ArchiveTree
31from ._dtypes import NumberType
33if TYPE_CHECKING:
34 import astropy.table
37class UnsupportedTableError(NotImplementedError):
38 """Exception raised if a table object has column types or structure that
39 are not supported by this library.
40 """
43class ColumnDefinitionModel(ArchiveTree):
44 """A model that describes a column in a table."""
46 name: str
47 """Name of the column."""
49 datatype: NumberType
50 """Type of the column."""
52 unit: Unit | None = pydantic.Field(default=None, exclude_if=operator.not_)
53 """Units of the column."""
55 description: str = pydantic.Field(default="", exclude_if=operator.not_)
56 """Extended description of the column."""
58 shape: tuple[int, ...] = pydantic.Field(default=(), exclude_if=operator.not_)
59 """Shape of a single cell in of this column.
61 An empty `tuple` is used to represent a scalar column.
62 """
64 is_variable_length: bool = pydantic.Field(default=False, exclude_if=operator.not_)
65 """Whether this column is a variable-length array."""
67 @classmethod
68 def from_record_dtype(cls, dtype: npt.DTypeLike) -> list[ColumnDefinitionModel]:
69 """Extract a list of column definitions from a structured numpy dtype.
71 Parameters
72 ----------
73 dtype
74 Object convertible to `numpy.dtype`.
75 """
76 dtype = np.dtype(dtype)
77 result: list[ColumnDefinitionModel] = []
78 if dtype.fields is None:
79 raise TypeError(f"{dtype} is not a structured dtype.")
80 for name, (field_dtype, *_) in dtype.fields.items():
81 # TODO: support string and variable-length array columns here.
82 try:
83 datatype, shape = NumberType.from_numpy_with_shape(field_dtype)
84 except TypeError:
85 raise UnsupportedTableError(f"Column type {field_dtype} is not supported.") from None
86 result.append(ColumnDefinitionModel.model_construct(name=name, datatype=datatype, shape=shape))
87 return result
89 def update_from_table(self, table: astropy.table.Table) -> None:
90 """Update the unit and description of this column from an astropy
91 table.
92 """
93 astropy_column: astropy.table.Column = table.columns[self.name]
94 self.unit = astropy.units.Unit(astropy_column.unit) if astropy_column.unit is not None else None
95 self.description = astropy_column.description
97 def update_table(self, table: astropy.table.Table) -> None:
98 """Update the unit and description of an astropy column from this
99 object.
100 """
101 astropy_column: astropy.table.Column = table.columns[self.name]
102 astropy_column.unit = self.unit
103 astropy_column.description = self.description
106class TableModel(ArchiveTree):
107 """Placeholder for an ASDF-like model for referencing binary tabular
108 data.
109 """
111 source: str | int
112 """Reference to the table data.
114 This is analogous to the ASDF ``ndarray`` field of the same name, i.e
115 for a FITS binary table, use "fits:EXTNAME[,EXTVER]" or "fits:INDEX"
116 (zero-indexed) to identify the HDU.
117 """
119 columns: list[ColumnDefinitionModel] = pydantic.Field(default_factory=list)
120 """Definitions of all columns."""
122 source_is_table: ClassVar[Literal[True]] = True
125class TableCellReferenceModel(ArchiveTree):
126 """A model that acts as a pointer to data in a table cell."""
128 model_config = pydantic.ConfigDict(frozen=True)
130 source: str | int
131 """Identifier for the table as a whole.
133 This is analogous to the ASDF ``ndarray`` field of the same name, i.e
134 for a FITS binary table, use "fits:EXTNAME[,EXTVER]" or "fits:INDEX"
135 (zero-indexed) to identify the HDU.
136 """
138 column: str
139 """Name of the column."""
141 row: int
142 """Row of the cell (zero-indexed)."""
144 source_is_table: ClassVar[Literal[True]] = True