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.

Files for approvals-validator, version 0.2.1
Filename, size File type Python version Upload date Hashes
Filename, size approvals_validator-0.2.1-py3-none-any.whl (11.5 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size approvals_validator-0.2.1.tar.gz (10.0 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page