Module continuous_delivery_scripts.assert_news

Checks if valid news files are created for changes in the project.

Functions

def find_news_files(git: GitWrapper,
root_dir: str,
news_dir: str) ‑> List[str]
Expand source code
def find_news_files(git: GitWrapper, root_dir: str, news_dir: str) -> List[str]:
    """Determines a list of all the news files which were added as part of the PR.

    Args:
        git: Instance of GitWrapper.
        root_dir: Root directory of the project.
        news_dir: Relative path to news directory.

    Returns:
        list: list of absolute paths to news files
    """
    files_changed = git.list_files_added_to_current_commit()
    # To speed up the process, we first look at files added to the current commit.
    # If no news files were added, then we check for addition on the branch.
    # Relies on the fact GitWrapper returns paths that are always relative
    # to the project root.
    news_prefix = news_dir.replace("\\", "/").rstrip("/") + "/"
    added_news_files = [file_path for file_path in files_changed if file_path.startswith(news_prefix)]
    if len(added_news_files) == 0:
        files_changed = git.list_files_added_on_current_branch()
        added_news_files = [file_path for file_path in files_changed if file_path.startswith(news_prefix)]
    return [str(pathlib.Path(root_dir, file_path)) for file_path in added_news_files]

Determines a list of all the news files which were added as part of the PR.

Args
-----=
git
Instance of GitWrapper.
root_dir
Root directory of the project.
news_dir
Relative path to news directory.
Returns
-----=
list
list of absolute paths to news files
def generate_news_file(git: GitWrapper,
news_dir: pathlib.Path) ‑> pathlib.Path
Expand source code
def generate_news_file(git: GitWrapper, news_dir: pathlib.Path) -> pathlib.Path:
    """Adds a news file if the branch corresponds to an dependency update.

    Args:
        git: Instance of GitWrapper.
        news_dir: path to news directory.
    """
    current_branch = str(git.get_current_branch())
    is_dependency_update, groups = git.is_current_branch_of_type(
        str(configuration.get_value(ConfigurationVariable.DEPENDENCY_UPDATE_BRANCH_PATTERN))
    )
    if not is_dependency_update:
        raise EnvironmentError(f"Branch {current_branch} must contain a news file.")
    if not configuration.get_value(ConfigurationVariable.AUTOGENERATE_NEWS_FILE_ON_DEPENDENCY_UPDATE):
        raise EnvironmentError(f"Branch {current_branch} must contain a news file.")

    list_groups = _convert_to_string_iter(groups)
    message = str(configuration.get_value(ConfigurationVariable.DEPENDENCY_UPDATE_NEWS_MESSAGE)).format(
        message=", ".join(list_groups)
    )
    logger.info(f"Generating a news file with content: {message}...")
    return create_news_file(
        str(news_dir),
        None,
        message,
        configuration.get_value(ConfigurationVariable.DEPENDENCY_UPDATE_NEWS_TYPE),
    )

Adds a news file if the branch corresponds to an dependency update.

Args
-----=
git
Instance of GitWrapper.
news_dir
path to news directory.
def main() ‑> None
Expand source code
def main() -> None:
    """Asserts the new PR comprises at least one news file and it adheres to the required standard."""
    parser = argparse.ArgumentParser(description="Check correctly formatted news files exist on feature branch.")
    parser.add_argument("-b", "--current-branch", help="Name of the current branch", nargs="?")
    parser.add_argument("-l", "--local", action="store_true", help="perform checks directly on local repository")
    parser.add_argument("-v", "--verbose", action="count", default=0, help="Verbosity, by default errors are reported.")
    args = parser.parse_args()
    set_log_level(args.verbose)

    repository: ContextManager[GitWrapper]
    if args.local:
        repository = LocalProjectRepository()
    else:
        repository = ProjectTempClone(desired_branch_name=args.current_branch)
    with repository as git:
        if git.is_current_branch_feature():
            root_dir = configuration.get_value(ConfigurationVariable.PROJECT_ROOT)
            absolute_news_dir = configuration.get_value(ConfigurationVariable.NEWS_DIR)
            news_dir = str(pathlib.Path(absolute_news_dir).relative_to(root_dir))
            try:
                validate_news_files(git=git, news_dir=news_dir, root_dir=str(git.root))
            except MissingNewsFileError as e:
                log_exception(logger, e)
                try:
                    news_file = generate_news_file(git, git.get_corresponding_path(pathlib.Path(news_dir)))
                    _commit_news_file(git, news_file, args.local)
                except Exception as e2:
                    log_exception(logger, e2)
                sys.exit(1)
            except Exception as e:
                log_exception(logger, e)
                sys.exit(1)

Asserts the new PR comprises at least one news file and it adheres to the required standard.

def validate_news_file(absolute_path: pathlib.Path | str) ‑> None
Expand source code
def validate_news_file(absolute_path: Union[pathlib.Path, str]) -> None:
    """Applies NewsFileValidator validation logic to news file."""
    NewsFileValidator(absolute_path).validate()

Applies NewsFileValidator validation logic to news file.

def validate_news_files(git: GitWrapper,
root_dir: str,
news_dir: str) ‑> None
Expand source code
def validate_news_files(git: GitWrapper, root_dir: str, news_dir: str) -> None:
    """Checks that news files exist and pass validation checks.

    Args:
        git: Instance of GitWrapper.
        root_dir: Root directory of the project.
        news_dir: Relative path to news directory.
    """
    added_news_files = find_news_files(git=git, news_dir=news_dir, root_dir=root_dir)
    if not added_news_files:
        raise MissingNewsFileError(f"PR must contain a news file in {news_dir}. See README.md.")
    for absolute_file_path in added_news_files:
        validate_news_file(absolute_file_path)

Checks that news files exist and pass validation checks.

Args
-----=
git
Instance of GitWrapper.
root_dir
Root directory of the project.
news_dir
Relative path to news directory.

Classes

class MissingNewsFileError (*args, **kwargs)
Expand source code
class MissingNewsFileError(FileNotFoundError):
    """The branch contains no added news file."""

The branch contains no added news file.

Ancestors

  • builtins.FileNotFoundError
  • builtins.OSError
  • builtins.Exception
  • builtins.BaseException
class NewsFileValidator (absolute_path: pathlib.Path | str)
Expand source code
class NewsFileValidator:
    """Verifies individual news files."""

    def __init__(self, absolute_path: Union[pathlib.Path, str]) -> None:
        """Creates a new instance of NewsFileValidator.

        Args:
            absolute_path: the absolute path to the news file
        """
        self._news_file_path = pathlib.Path(absolute_path)
        self._basename = self._news_file_path.name

    def validate_file_name(self) -> None:
        """Ensures the news file follows the naming rules."""
        if re.match(NEWS_FILE_NAME_REGEX, self._basename) is None:
            raise ValueError(
                f'Incorrect news file name "{self._basename}".'
                f' It doesn\'t match the following regex: "{NEWS_FILE_NAME_REGEX}".'
            )

    def validate_file_contents(self) -> None:
        """Ensures the news file is not empty and not longer than one line."""
        file_content = self._news_file_path.read_text()
        if file_content.strip() == "":
            raise ValueError(f'Empty news file "{self._basename}".')
        if len(file_content.splitlines()) > 1:
            raise ValueError(f'News file "{self._basename}" contains more than one line.')

    def validate(self) -> None:
        """Runs all validators."""
        logger.info(f"Verifying {self._basename}")
        self.validate_file_name()
        self.validate_file_contents()

Verifies individual news files.

Creates a new instance of NewsFileValidator.

Args
-----=
absolute_path
the absolute path to the news file

Methods

def validate(self) ‑> None
Expand source code
def validate(self) -> None:
    """Runs all validators."""
    logger.info(f"Verifying {self._basename}")
    self.validate_file_name()
    self.validate_file_contents()

Runs all validators.

def validate_file_contents(self) ‑> None
Expand source code
def validate_file_contents(self) -> None:
    """Ensures the news file is not empty and not longer than one line."""
    file_content = self._news_file_path.read_text()
    if file_content.strip() == "":
        raise ValueError(f'Empty news file "{self._basename}".')
    if len(file_content.splitlines()) > 1:
        raise ValueError(f'News file "{self._basename}" contains more than one line.')

Ensures the news file is not empty and not longer than one line.

def validate_file_name(self) ‑> None
Expand source code
def validate_file_name(self) -> None:
    """Ensures the news file follows the naming rules."""
    if re.match(NEWS_FILE_NAME_REGEX, self._basename) is None:
        raise ValueError(
            f'Incorrect news file name "{self._basename}".'
            f' It doesn\'t match the following regex: "{NEWS_FILE_NAME_REGEX}".'
        )

Ensures the news file follows the naming rules.