Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jmromer/approvals_validator

A CLI to validate that sufficient approvals have been received for a changeset.
https://github.com/jmromer/approvals_validator

bash demo docker pytest python python-3-8

Last synced: 25 days ago
JSON representation

A CLI to validate that sufficient approvals have been received for a changeset.

Awesome Lists containing this project

README

        

Approval Validator
==================

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

**Table of Contents**

- [Approval Validator](#approval-validator)
- [Installation](#installation)
- [Usage](#usage)
- [Requirements](#requirements)
- [Dependencies](#dependencies)
- [Tests](#tests)
- [Design Notes](#design-notes)
- [`ChangeSet`, `ChangedDirectory`](#changeset-changeddirectory)
- [`file_utils`](#fileutils)
- [`exceptions`](#exceptions)
- [Performance](#performance)

Installation
------------

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`.

Usage
-----

```text
% 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.

Example:

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

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

Requirements
------------

- Python >= 3.8.0 (for `functools.cached_property`)

A `.tool-versions` file is included for [asdf][asdf] users.

[asdf]: https://github.com/asdf-vm/asdf

Dependencies
------------

- [click](https://github.com/pallets/click)
- [pytest](https://pytest.org/en/latest/)

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

For reference, `bin/setup` usage instructions:

```text
Usage:
./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
```

Tests
-----

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.

```text
% ./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/Follow.java,data/repo/src/com/twitter/user/User.java
./validate_approvals --approvers alovelace --changed-files data/repo/src/com/twitter/follow/Follow.java
./validate_approvals --approvers eclarke --changed-files data/repo/src/com/twitter/follow/Follow.java
./validate_approvals --approvers alovelace,eclarke --changed-files data/repo/src/com/twitter/follow/Follow.java
./validate_approvals --approvers mfox --changed-files data/repo/src/com/twitter/tweet/Tweet.java

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/__init__.py 3 0 100%
approval_validator/change_set.py 14 14 0%
approval_validator/changed_directory.py 30 0 100%
approval_validator/cli_utils.py 14 14 0%
approval_validator/exceptions.py 9 2 78%
approval_validator/file_utils.py 74 0 100%
approval_validator/tests/__init__.py 0 0 100%
approval_validator/tests/changed_directory_test.py 37 0 100%
approval_validator/tests/file_utils_test.py 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`.

```py
# approval_validator/changed_directory.py L24-37

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

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

### `file_utils`

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

### `exceptions`

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

```py
# approval_validator/exceptions.py 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/file_utils.py)
"""
return f"\n\n{cleandoc(message)}"
```

Performance
-----------

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

- [`functools.cached_property`][cp]
- [`functools.lru_cache`][lru]

[cp]: https://docs.python.org/3/library/functools.html#functools.cached_property
[lru]: https://docs.python.org/3/library/functools.html#functools.lru_cache