diff --git a/setup.cfg b/setup.cfg index d3d27c1..74a21c4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ classifiers = [options] include_package_data = True install_requires = + packaging sphinx towncrier >= 19.2 package_dir = diff --git a/src/sphinxcontrib/towncrier/_fragment_discovery.py b/src/sphinxcontrib/towncrier/_fragment_discovery.py index 4903366..7547de4 100644 --- a/src/sphinxcontrib/towncrier/_fragment_discovery.py +++ b/src/sphinxcontrib/towncrier/_fragment_discovery.py @@ -2,11 +2,14 @@ from functools import lru_cache +from importlib.metadata import version from pathlib import Path from typing import Optional, Set from sphinx.util import logging +from packaging.version import Version + try: # pylint: disable=no-name-in-module @@ -17,7 +20,9 @@ find_fragments, ) -from ._towncrier import get_towncrier_config # noqa: WPS436 +from ._towncrier import ( # noqa: WPS436 + get_towncrier_config, get_towncrier_config_as_dict, +) logger = logging.getLogger(__name__) @@ -42,11 +47,21 @@ def _find_config_file(base: Path) -> Path: # pylint: disable=fixme # FIXME: refactor `lookup_towncrier_fragments` to drop noqas @lru_cache(maxsize=1, typed=True) # noqa: WPS210 -def lookup_towncrier_fragments( # noqa: WPS210 +def lookup_towncrier_fragments( working_dir: Optional[str] = None, config_path: Optional[str] = None, ) -> Set[Path]: """Emit RST-formatted Towncrier changelog fragment paths.""" + tc_version = Version(version('towncrier')) + if tc_version >= Version('24.7.0'): + return _lookup_towncrier_fragments_post24_7(working_dir, config_path) + return _lookup_towncrier_fragments_pre24_7(working_dir, config_path) + + +# used for towncrier version 24.7 and above +def _lookup_towncrier_fragments_post24_7( # noqa: WPS210 + working_dir: Optional[str] = None, config_path: Optional[str] = None, +) -> Set[Path]: project_path = Path.cwd() if working_dir is None else Path(working_dir) final_config_path = ( @@ -66,6 +81,39 @@ def lookup_towncrier_fragments( # noqa: WPS210 ) return set() + fragment_dir = (towncrier_config.directory or 'newsfragments') + fragment_base_directory = project_path / fragment_dir + + _fragments, fragment_filenames = find_fragments( + str(fragment_base_directory), towncrier_config, strict=False, + ) + + return {Path(fname[0]) for fname in fragment_filenames} + + +# used for versions of towncrier before 24.7 +def _lookup_towncrier_fragments_pre24_7( # noqa: WPS210 + working_dir: Optional[str] = None, config_path: Optional[str] = None, +) -> Set[Path]: + project_path = Path.cwd() if working_dir is None else Path(working_dir) + + final_config_path = ( + _resolve_spec_config(project_path, config_path) + or _find_config_file(project_path) + ) + + try: + towncrier_config = get_towncrier_config_as_dict( + project_path, + final_config_path, + ) + except KeyError as key_err: + # NOTE: The error is missing key 'towncrier' or similar + logger.warning( + f'Missing key {key_err!s} in file {final_config_path!s}', + ) + return set() + fragment_directory: Optional[str] = 'newsfragments' try: fragment_base_directory = project_path / towncrier_config['directory'] diff --git a/src/sphinxcontrib/towncrier/_towncrier.py b/src/sphinxcontrib/towncrier/_towncrier.py index db5c436..1ac0d36 100644 --- a/src/sphinxcontrib/towncrier/_towncrier.py +++ b/src/sphinxcontrib/towncrier/_towncrier.py @@ -1,9 +1,12 @@ """Towncrier related shims.""" from dataclasses import asdict as _dataclass_to_dict +from importlib.metadata import version from pathlib import Path from typing import Any, Dict, Union +from packaging.version import Version + try: # Towncrier >= 22.8.0rc1 @@ -19,6 +22,20 @@ def get_towncrier_config( + project_path: Path, + final_config_path: Union[Path, None], +) -> Any: + """Return the towncrier config in native format.""" + tc_version = Version(version('towncrier')) + if tc_version >= Version('22.12.0rc1'): + return load_config_from_file(str(project_path), str(final_config_path)) + raise NotImplementedError( + 'Towncrier Config is not available before version 22.12.0rc1, consider ' + 'using "get_towncrier_config_as_dict()" instead', + ) + + +def get_towncrier_config_as_dict( project_path: Path, final_config_path: Union[Path, None], ) -> Dict[str, Any]: # FIXME: add a better type # pylint: disable=fixme