Coverage for python / lsst / images / serialization / _tables.py: 67%
60 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 02:28 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 02:28 -0700
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 ._dtypes import NumberType
32if TYPE_CHECKING:
33 import astropy.table
36class UnsupportedTableError(NotImplementedError):
37 """Exception raised if a table object has column types or structure that
38 are not supported by this library.
39 """
42class ColumnDefinitionModel(pydantic.BaseModel):
43 """A model that describes a column in a table."""
45 name: str
46 """Name of the column."""
48 datatype: NumberType
49 """Type of the column."""
51 unit: Unit | None = pydantic.Field(default=None, exclude_if=operator.not_)
52 """Units of the column."""
54 description: str = pydantic.Field(default="", exclude_if=operator.not_)
55 """Extended description of the column."""
57 shape: tuple[int, ...] = pydantic.Field(default=(), exclude_if=operator.not_)
58 """Shape of a single cell in of this column.
60 An empty `tuple` is used to represent a scalar column.
61 """
63 is_variable_length: bool = pydantic.Field(default=False, exclude_if=operator.not_)
64 """Whether this column is a variable-length array."""
66 @classmethod
67 def from_record_dtype(cls, dtype: npt.DTypeLike) -> list[ColumnDefinitionModel]:
68 """Extract a list of column definitions from a structured numpy dtype.
70 Parameters
71 ----------
72 dtype
73 Object convertible to `numpy.dtype`.
74 """
75 dtype = np.dtype(dtype)
76 result: list[ColumnDefinitionModel] = []
77 if dtype.fields is None:
78 raise TypeError(f"{dtype} is not a structured dtype.")
79 for name, (field_dtype, *_) in dtype.fields.items():
80 # TODO: support string and variable-length array columns here.
81 try:
82 datatype, shape = NumberType.from_numpy_with_shape(field_dtype)
83 except TypeError:
84 raise UnsupportedTableError(f"Column type {field_dtype} is not supported.") from None
85 result.append(ColumnDefinitionModel.model_construct(name=name, datatype=datatype, shape=shape))
86 return result
88 def update_from_table(self, table: astropy.table.Table) -> None:
89 """Update the unit and description of this column from an astropy
90 table.
91 """
92 astropy_column: astropy.table.Column = table.columns[self.name]
93 self.unit = astropy.units.Unit(astropy_column.unit) if astropy_column.unit is not None else None
94 self.description = astropy_column.description
96 def update_table(self, table: astropy.table.Table) -> None:
97 """Update the unit and description of an astropy column from this
98 object.
99 """
100 astropy_column: astropy.table.Column = table.columns[self.name]
101 astropy_column.unit = self.unit
102 astropy_column.description = self.description
105class TableModel(pydantic.BaseModel):
106 """Placeholder for an ASDF-like model for referencing binary tabular
107 data.
108 """
110 source: str | int
111 """Reference to the table data.
113 This is analogous to the ASDF ``ndarray`` field of the same name, i.e
114 for a FITS binary table, use "fits:EXTNAME[,EXTVER]" or "fits:INDEX"
115 (zero-indexed) to identify the HDU.
116 """
118 columns: list[ColumnDefinitionModel] = pydantic.Field(default_factory=list)
119 """Definitions of all columns."""
121 source_is_table: ClassVar[Literal[True]] = True
124class TableCellReferenceModel(pydantic.BaseModel):
125 """A model that acts as a pointer to data in a table cell."""
127 model_config = pydantic.ConfigDict(frozen=True)
129 source: str | int
130 """Identifier for the table as a whole.
132 This is analogous to the ASDF ``ndarray`` field of the same name, i.e
133 for a FITS binary table, use "fits:EXTNAME[,EXTVER]" or "fits:INDEX"
134 (zero-indexed) to identify the HDU.
135 """
137 column: str
138 """Name of the column."""
140 row: int
141 """Row of the cell (zero-indexed)."""
143 source_is_table: ClassVar[Literal[True]] = True