Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move parsing into parse subcommand (introduce argparse) #25

Merged
merged 9 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- *Action required:* Move existing behaviour under "parse" subcommand.
Invocations of `mypy-json-report` should now be replaced with `mypy-json-report parse`.
- Add `parse --indentation` flag to grant control over how much indentation is used in the JSON report.
- Use GA version of Python 3.11 in test matrix.

## v0.1.3 [2022-09-07]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Pipe the output of mypy through the `mypy-json-report` CLI app.
Store the output to a file, and commit it to your git repo.

```
mypy . --strict | mypy-json-report > known-mypy-errors.json
mypy . --strict | mypy-json-report parse > known-mypy-errors.json
git add known-mypy-errors.json
git commit -m "Add mypy errors lockfile"
```
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
- name: Run mypy
run: |
mypy . --strict | mypy-json-report > known-mypy-errors.json
mypy . --strict | mypy-json-report parse > known-mypy-errors.json
- name: Check for mypy changes
run: |
Expand Down
56 changes: 52 additions & 4 deletions mypy_json_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,63 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import enum
import json
import sys
from collections import Counter, defaultdict
from dataclasses import dataclass
from typing import Counter as CounterType, Dict, Iterator


class ErrorCodes(enum.IntEnum):
DEPRECATED = 1


def main() -> None:
print(produce_errors_report(sys.stdin))
"""
The primary entrypoint of the program.
Parses the CLI flags, and delegates to other functions as appropriate.
For details of how to invoke the program, call it with `--help`.
"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="subcommand")

parser.set_defaults(func=_no_command)

parse_parser = subparsers.add_parser(
"parse", help="Transform Mypy output into JSON."
)
parse_parser.add_argument(
"-i",
"--indentation",
type=int,
default=2,
help="Number of spaces to indent JSON output.",
)

parse_parser.set_defaults(func=_parse_command)

parsed = parser.parse_args()
parsed.func(parsed)


def _parse_command(args: argparse.Namespace) -> None:
"""Handle the `parse` command."""
errors = parse_errors_report(sys.stdin)
error_json = json.dumps(errors, sort_keys=True, indent=args.indentation)
print(error_json)


def _no_command(args: argparse.Namespace) -> None:
"""
Handle the lack of an explicit command.
This will be hit when the program is called without arguments.
"""
print("A subcommand is required. Pass --help for usage info.")
sys.exit(ErrorCodes.DEPRECATED)


@dataclass(frozen=True)
Expand All @@ -29,12 +77,12 @@ class MypyError:
message: str


def produce_errors_report(input_lines: Iterator[str]) -> str:
"""Given lines from mypy's output, return a JSON summary of error frequencies by file."""
def parse_errors_report(input_lines: Iterator[str]) -> Dict[str, Dict[str, int]]:
"""Given lines from mypy's output, return a summary of error frequencies by file."""
errors = _extract_errors(input_lines)
error_frequencies = _count_errors(errors)
structured_errors = _structure_errors(error_frequencies)
return json.dumps(structured_errors, sort_keys=True, indent=2)
return structured_errors


def _extract_errors(lines: Iterator[str]) -> Iterator[MypyError]:
Expand Down
9 changes: 4 additions & 5 deletions tests/test_mypy_json_report.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from io import StringIO

from mypy_json_report import produce_errors_report
from mypy_json_report import parse_errors_report


EXAMPLE_MYPY_STDOUT = """\
Expand All @@ -11,10 +10,10 @@
Found 2 errors in 1 file (checked 3 source files)"""


def test_report() -> None:
report = produce_errors_report(StringIO(EXAMPLE_MYPY_STDOUT))
def test_parse_errors_report() -> None:
report = parse_errors_report(StringIO(EXAMPLE_MYPY_STDOUT))

assert json.loads(report) == {
assert report == {
"mypy_json_report.py": {
'Call to untyped function "main" in typed context': 1,
"Function is missing a return type annotation": 1,
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ allowlist_externals =
commands_pre =
poetry install
commands =
poetry run bash -c "mypy . --strict | mypy-json-report > known-mypy-errors.json"
poetry run bash -c "mypy . --strict | mypy-json-report parse > known-mypy-errors.json"
git diff --exit-code known-mypy-errors.json