Skip to main content

A CLI tool to validate changeset approvals.

Project description

Approval Validator

A CLI to validate that sufficient approvals have been received for a changeset in the context of a project.

Table of Contents


The quickest way to use the tool in anger is to install it using pip: pip install approvals-validator.

Alternatively, run the executable from the project root. Install dependencies with bin/setup.


% validate_approvals --help

Usage: validate_approvals REQUIRED_FLAGS

  Validate that the correct approvals have been received to approve changes
  to the given files.

  Note: Multiple approvers and/or changed files can be passed as CSV strings.


    validate_approvals --approvers alovelace,eclarke --changed-files src/com/twitter/follow/

  -a, --approvers USERNAMES       Username(s) of approvals.  [required]
  -c, --changed-files FILE_PATHS  File paths. [required]
  -h, --help                      Show this message and exit.


  • Python >= 3.8.0 (for functools.cached_property)

A .tool-versions file is included for asdf users.


The test-runner script (./test) will attempt to install dependencies in a virtualenv at project root named ./env.

For reference, bin/setup usage instructions:

  ./bin/setup [OPTIONS] ENV

Install dependencies for `validate_approvals` in a virtualenv at project root.

Available environments:

 dev     Install all dependencies
 prod    Install minimal dependencies for running `validate_approvals`
 test    Install minimal and test dependencies

Available options:

 --silent  Run without verbose output


A test runner script is included to run the entire test suite and display code coverage metrics. Pass the --docker flag to (re-)build a Docker image and run tests with Docker.

Acceptances tests are written in Bash script, unit and integration tests in Python with pytest.

% ./test

Running acceptance tests...
./validate_approvals -c data/minimal/y/file -a B
./validate_approvals -c data/minimal/y/file -a A,C
./validate_approvals -c data/minimal/y/file -a D
./validate_approvals --approvers alovelace,ghopper --changed-files data/repo/src/com/twitter/follow/,data/repo/src/com/twitter/user/
./validate_approvals --approvers alovelace --changed-files data/repo/src/com/twitter/follow/
./validate_approvals --approvers eclarke --changed-files data/repo/src/com/twitter/follow/
./validate_approvals --approvers alovelace,eclarke --changed-files data/repo/src/com/twitter/follow/
./validate_approvals --approvers mfox --changed-files data/repo/src/com/twitter/tweet/

Running pytest tests...

Running mypy on 11 files... done with status 0
Success: no issues found in 11 source files
...............................                                   [100%]

---------- coverage: platform darwin, python 3.8.0-final-0 -----------
Name                                                 Stmts   Miss  Cover
approval_validator/                           3      0   100%
approval_validator/                        14     14     0%
approval_validator/                 30      0   100%
approval_validator/                         14     14     0%
approval_validator/                         9      2    78%
approval_validator/                        74      0   100%
approval_validator/tests/                     0      0   100%
approval_validator/tests/      37      0   100%
approval_validator/tests/             59      4    93%
TOTAL                                                  240     34    86%

Design Notes

The script entrypoint is the CLI function in the executable validate_approvals.

The approval_validator.cli_utils module defines how arguments are parsed.

ChangeSet, ChangedDirectory

The main classes are ChangeSet and ChangedDirectory.

The former models an entire changeset (i.e., all the files passed via the --changed-files flag), the latter each individual entry in the list of files passed to --changed_files.

# approval_validator/ L24-37

def affected_directories(self) -> Tuple[Path, ...]:
    return util.find_dependent_dirs(

def approved(self) -> bool:
    Return true if sufficient approval has been received for this
    for impacted_dir in self.impacted_directories:
        if not self.__change_approved(impacted_dir):
            return False
    return True


File-parsing and directory-traversal logic is housed in the file_utils module.


Defines ApprovalValidatorError, the base class for library-specific exceptions, and ProjectRootNotFoundError, which is raised when a project root can't be found.

# approval_validator/ L8-20

class ProjectRootNotFoundError(ApprovalValidatorError):
    """Raised when a project root can't be found."""
    def __init__(self, start_dir):
        self.start_dir = start_dir

    def __str__(self):
        message = f"""
        Project root search failed. Started from: {self.start_dir}

        Note: We detect the presence of a project root using the entries of
        PROJECT_ROOT_FILES. (see: approval_validator/
        return f"\n\n{cleandoc(message)}"


Caching improved running time by ~20%. The following facilities are used:

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

approvals_validator-0.2.1.tar.gz (10.0 kB view hashes)

Uploaded source

Built Distribution

approvals_validator-0.2.1-py3-none-any.whl (11.5 kB view hashes)

Uploaded py3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page