Coverage for python/felis/browser/config.py: 98%

39 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-27 01:40 -0700

1"""Site configuration for the schema browser.""" 

2 

3# This file is part of felis. 

4# 

5# Developed for the LSST Data Management System. 

6# This product includes software developed by the LSST Project 

7# (https://www.lsst.org). 

8# See the COPYRIGHT file at the top-level directory of this distribution 

9# for details of code ownership. 

10# 

11# This program is free software: you can redistribute it and/or modify 

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

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

14# (at your option) any later version. 

15# 

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

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

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

19# GNU General Public License for more details. 

20# 

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

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

23 

24from __future__ import annotations 

25 

26from pathlib import Path 

27 

28import yaml 

29from pydantic import BaseModel, ConfigDict, FilePath, HttpUrl 

30 

31__all__ = ["Maintainer", "SiteConfig"] 

32 

33 

34class Maintainer(BaseModel): 

35 """The organization maintaining the schemas, named in the page footer.""" 

36 

37 model_config = ConfigDict(extra="forbid") 

38 

39 name: str 

40 """Name of the organization.""" 

41 

42 url: HttpUrl | None = None 

43 """Website of the organization, linked from its name; none by default.""" 

44 

45 

46class SiteConfig(BaseModel): 

47 """Configuration of how a generated schema browser site looks. 

48 

49 Read from a YAML file with `from_file`; every setting is optional. 

50 """ 

51 

52 model_config = ConfigDict(extra="forbid") 

53 

54 title: str = "Home" 

55 """Site title: the first breadcrumb item, and the start of every page 

56 title. 

57 """ 

58 

59 description: str | None = None 

60 """Site description, shown on the home page above the table of schemas; 

61 none by default. 

62 """ 

63 

64 stylesheets: list[FilePath] = [] 

65 """Stylesheets applied after the built-in one, in order, so later ones 

66 take precedence. Each is copied into the site's ``assets`` directory. 

67 """ 

68 

69 icon: FilePath | None = None 

70 """Site icon (an image file), shown at the top left of every page and 

71 linking to the home page; none by default. It is copied into the site's 

72 ``assets`` directory. 

73 """ 

74 

75 favicon: FilePath | None = None 

76 """Icon shown in the browser's tab for every page (an image file, such as 

77 a PNG, ICO or SVG); none by default. It is copied into the site's 

78 ``assets`` directory. 

79 """ 

80 

81 maintainer: Maintainer | None = None 

82 """Organization maintaining the schemas, shown as "Maintained by" in the 

83 footer of every page; none by default. 

84 """ 

85 

86 show_empty_columns: bool = False 

87 """Show every column field in each table's columns table, including 

88 fields that none of the table's columns set; by default those are left 

89 out (``name``, ``datatype``, ``description``, ``unit`` and ``nullable`` 

90 are always shown). A field set only to its default, such as 

91 ``tap:principal: 0``, counts as not set. 

92 """ 

93 

94 diagrams: bool = False 

95 """Show a relationship diagram, drawn from the foreign keys, on the page 

96 of each schema that has them. The diagrams are drawn in the browser by 

97 Mermaid, loaded from a CDN. 

98 """ 

99 

100 @classmethod 

101 def from_file(cls, path: Path) -> SiteConfig: 

102 """Read a site configuration from a YAML file. 

103 

104 Parameters 

105 ---------- 

106 path 

107 The configuration file. Relative stylesheet, icon and favicon 

108 paths in it are relative to the file's directory. 

109 

110 Returns 

111 ------- 

112 `SiteConfig` 

113 The configuration. 

114 

115 Raises 

116 ------ 

117 pydantic.ValidationError 

118 Raised if the file has unknown settings, values of the wrong type, 

119 or stylesheets, an icon or a favicon that don't exist. 

120 """ 

121 data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} 

122 # Make the file paths relative to the configuration file's directory. 

123 # Anything that isn't a dict, list or string is left for validation 

124 # to report. 

125 if isinstance(data, dict): 125 ↛ 131line 125 didn't jump to line 131 because the condition on line 125 was always true

126 if isinstance(data.get("stylesheets"), list): 

127 data["stylesheets"] = [path.parent / str(stylesheet) for stylesheet in data["stylesheets"]] 

128 for key in ("icon", "favicon"): 

129 if isinstance(data.get(key), str): 

130 data[key] = path.parent / data[key] 

131 return cls.model_validate(data)