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


% ./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.


Running the executable (./validate_approvals) or tests (./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


Acceptances tests are written in Bash script, unit and integration tests in Python with pytest. To run all of them and display code coverage info, issue ./test from the project root.

% ./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 12 files... done with status 0
Success: no issues found in 12 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/                         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                                                  254     48    81%

Design Notes

The entrypoint is approvals_validator/, which defines the CLI. The 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.0.1.tar.gz (9.5 kB view hashes)

Uploaded source

Built Distribution

approvals_validator-0.0.1-py3-none-any.whl (10.6 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