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

1"""Project information based on the pyproject.toml.""" 

2from io import TextIOWrapper # noqa: F401 

3from pathlib import Path 

4from typing import Any, override 

5 

6from project_meta.project_info import _ProjectInfoBase, _ProjectKey 

7 

8 

9class ProjectInfoPyProject(_ProjectInfoBase): 

10 """Project information based on the pyproject.toml.""" 

11 

12 def __init__(self, project_root: Path, pyproject_data: dict[str, Any]) -> None: 

13 """Initialize the project information from pyproject.toml data. 

14 

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 

21 

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}") 

37 

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 

44 

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) 

50 

51 def _get_version(self) -> str: 

52 """Read the version from the pyproject.toml [project][version], dynamic section [tool.setuptools.dynamic] or 'VERSION' file. 

53 

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)