Coverage for python/felis/browser/config.py: 98%
39 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-04 01:32 -0800
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-04 01:32 -0800
1"""Site configuration for the schema browser."""
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/>.
24from __future__ import annotations
26from pathlib import Path
28import yaml
29from pydantic import BaseModel, ConfigDict, FilePath, HttpUrl
31__all__ = ["Maintainer", "SiteConfig"]
34class Maintainer(BaseModel):
35 """The organization maintaining the schemas, named in the page footer."""
37 model_config = ConfigDict(extra="forbid")
39 name: str
40 """Name of the organization."""
42 url: HttpUrl | None = None
43 """Website of the organization, linked from its name; none by default."""
46class SiteConfig(BaseModel):
47 """Configuration of how a generated schema browser site looks.
49 Read from a YAML file with `from_file`; every setting is optional.
50 """
52 model_config = ConfigDict(extra="forbid")
54 title: str = "Home"
55 """Site title: the first breadcrumb item, and the start of every page
56 title.
57 """
59 description: str | None = None
60 """Site description, shown on the home page above the table of schemas;
61 none by default.
62 """
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 """
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 """
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 """
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 """
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 """
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 """
100 @classmethod
101 def from_file(cls, path: Path) -> SiteConfig:
102 """Read a site configuration from a YAML file.
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.
110 Returns
111 -------
112 `SiteConfig`
113 The configuration.
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)