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

47 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-17 10:27 +0000

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 DESCR_MIN_LENGTH, Column, 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: str = Field(..., min_length=DESCR_MIN_LENGTH) 

41 """Redefine description to make it required and non-empty.""" 

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: str = Field(..., min_length=DESCR_MIN_LENGTH) 

53 """Redefine description so that it is 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 description: str = Field(..., min_length=DESCR_MIN_LENGTH) 

80 """Redefine description to make it required and non-empty. 

81 """ 

82 

83 tables: Sequence[RspTable] 

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

85 

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

87 @classmethod 

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

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

90 table_indicies = set() 

91 for table in sch.tables: 

92 table_index = table.tap_table_index 

93 if table_index is not None: 

94 if table_index in table_indicies: 

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

96 table_indicies.add(table_index) 

97 return sch 

98 

99 

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

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

102 if schema_name == "default": 

103 return Schema 

104 elif schema_name == "RSP": 

105 return RspSchema 

106 else: 

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