Coverage for src / project_meta / project_info_pyproject.py: 0%
49 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 21:45 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 21:45 +0000
1"""Project information based on the pyproject.toml."""
2from io import TextIOWrapper # noqa: F401
3from pathlib import Path
4from typing import Any, override
6from project_meta.project_info import _ProjectInfoBase, _ProjectKey
9class ProjectInfoPyProject(_ProjectInfoBase):
10 """Project information based on the pyproject.toml."""
12 def __init__(self, project_root: Path, pyproject_data: dict[str, Any]) -> None:
13 """Initialize the project information from pyproject.toml data.
15 :param project_root: Path to the project's root directory.
16 :param pyproject_data: Parsed pyproject.toml data.
17 """
18 super().__init__()
19 self.project_root = project_root
20 self.pyproject_data = pyproject_data
22 @override
23 def _get_item_impl(self, project_key: _ProjectKey) -> str:
24 match project_key:
25 case _ProjectKey.NAME:
26 return self._get_value(['project', 'name'])
27 case _ProjectKey.VERSION:
28 return self._get_version()
29 case _ProjectKey.DESCRIPTION:
30 return self._get_value(['project', 'description'])
31 case _ProjectKey.AUTHOR:
32 return ", ".join(author.get("name") for author in self._get_pyproject(['project', 'authors']))
33 case _ProjectKey.HOMEPAGE_URL:
34 return self._get_value(['urls', 'homepage'])
35 case _:
36 raise ValueError(f"Unknown project key: {project_key}")
38 def _get_pyproject(self, elements: list[str]) -> Any:
39 # Navigate through nested elements
40 data = self.pyproject_data
41 for element in elements:
42 data = data.get(element, {})
43 return data
45 def _get_value(self, elements: list[str]) -> str:
46 value = self._get_pyproject(elements)
47 if not value:
48 raise ValueError(f"[{']['.join(elements)}] was not found in pyproject.toml")
49 return str(value)
51 def _get_version(self) -> str:
52 """Read the version from the pyproject.toml [project][version], dynamic section [tool.setuptools.dynamic] or 'VERSION' file.
54 This does not support [tool.setuptools_scm] version scheme.
55 :return: Project version.
56 :raises ValueError: If the version is not found in any of the expected locations.
57 """
58 # Try to read version from [project][version] section first
59 try:
60 return self._get_value(['project', 'version'])
61 except ValueError:
62 pass
63 try:
64 # Fallback: Try to read it from [tool.setuptools.dynamic] section
65 version_file_name = self.project_root / self._get_value(['tool', 'setuptools', 'dynamic', 'version', 'file'])
66 except ValueError:
67 # Fallback: Try the default VERSION file
68 version_file_name = self.project_root / "VERSION"
69 try:
70 with open(file=version_file_name, encoding="utf-8") as file_ver: # type: TextIOWrapper
71 project_version = str(file_ver.read().strip())
72 except FileNotFoundError as err:
73 raise ValueError("Project version is not defined by [project][version] or [tool.setuptools.dynamic]") from err
74 return str(project_version)