Coverage for python/felis/validation.py: 56%

45 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-02 02:10 -0700

1# This file is part of felis. 

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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24import logging 

25from collections.abc import Sequence 

26from typing import Any 

27 

28from pydantic import Field, model_validator 

29 

30from .datamodel import Column, DescriptionStr, Schema, Table 

31 

32logger = logging.getLogger(__name__) 

33 

34__all__ = ["RspColumn", "RspSchema", "RspTable", "get_schema"] 

35 

36 

37class RspColumn(Column): 

38 """Column for RSP data validation.""" 

39 

40 description: DescriptionStr 

41 """Redefine description to make it required.""" 

42 

43 

44class RspTable(Table): 

45 """Table for RSP data validation. 

46 

47 The list of columns is overridden to use RspColumn instead of Column. 

48 

49 Tables for the RSP must have a TAP table index and a valid description. 

50 """ 

51 

52 description: DescriptionStr 

53 """Redefine description to make it required.""" 

54 

55 tap_table_index: int = Field(..., alias="tap:table_index") 

56 """Redefine the TAP_SCHEMA table index so that it is required.""" 

57 

58 columns: Sequence[RspColumn] 

59 """Redefine columns to include RSP validation.""" 

60 

61 @model_validator(mode="after") # type: ignore[arg-type] 

62 @classmethod 

63 def check_tap_principal(cls: Any, tbl: "RspTable") -> "RspTable": 

64 """Check that at least one column is flagged as 'principal' for 

65 TAP purposes. 

66 """ 

67 for col in tbl.columns: 

68 if col.tap_principal == 1: 

69 return tbl 

70 raise ValueError(f"Table '{tbl.name}' is missing at least one column designated as 'tap:principal'") 

71 

72 

73class RspSchema(Schema): 

74 """Schema for RSP data validation. 

75 

76 TAP table indexes must be unique across all tables. 

77 """ 

78 

79 tables: Sequence[RspTable] 

80 """Redefine tables to include RSP validation.""" 

81 

82 @model_validator(mode="after") # type: ignore[arg-type] 

83 @classmethod 

84 def check_tap_table_indexes(cls: Any, sch: RspSchema) -> RspSchema: 

85 """Check that the TAP table indexes are unique.""" 

86 table_indicies = set() 

87 for table in sch.tables: 

88 table_index = table.tap_table_index 

89 if table_index is not None: 

90 if table_index in table_indicies: 

91 raise ValueError(f"Duplicate 'tap:table_index' value {table_index} found in schema") 

92 table_indicies.add(table_index) 

93 return sch 

94 

95 

96def get_schema(schema_name: str) -> type[Schema]: 

97 """Get the schema class for the given name.""" 

98 if schema_name == "default": 

99 return Schema 

100 elif schema_name == "RSP": 

101 return RspSchema 

102 else: 

103 raise ValueError(f"Unknown schema name '{schema_name}'")