Skip to content

Commit

Permalink
Add error code parser/crawler (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicoretti authored Aug 9, 2023
1 parent 9486161 commit f10b253
Show file tree
Hide file tree
Showing 12 changed files with 886 additions and 14 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ jobs:
poetry-version: 1.4.0
- name: Poetry install
run: poetry install

- name: Poetry build
run: poetry build
- name: Poetry run tests

- name: Run tests for old API
run: poetry run pytest tests

- name: Run tests for new API and parser
run: poetry run pytest test
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Error Reporting Python

This project contains a python library for describing Exasol error messages.
This library lets you define errors with a uniform set of attributes.
Furthermore, the error message is implemented to be parseable,
This project contains a python library for describing Exasol error messages.
This library lets you define errors with a uniform set of attributes.
Furthermore, the error message is implemented to be parseable,
so that you can extract an error catalog from the code.


## In a Nutshell

Create an error object:

```python
exa_error_obj = ExaError.message_builder('E-TEST-1')\
.message("Not enough space on device {{device}}.")\
.mitigation("Delete something from {{device}}.")\
.mitigation("Create larger partition.")\
exa_error_obj = ExaError.message_builder('E-TEST-1')
.message("Not enough space on device {{device}}.")
.mitigation("Delete something from {{device}}.")
.mitigation("Create larger partition.")
.parameter("device", "/dev/sda1", "name of the device")
```

Expand All @@ -24,15 +24,40 @@ print(exa_error_obj)
```

Result:

```
E-TEST-1: Not enough space on device '/dev/sda1'. Known mitigations:
* Delete something from '/dev/sda1'.
* Create larger partition.
```


Check out the [user guide](doc/user_guide/user_guide.md) for more details.

## Tooling

The `error-reporting-python` library comes with a command line tool (`ec`) which also can be invoked
by using its package/module entry point (`python -m exasol.error`).
For detailed information about the usage consider consulting the help `ec --help` or `python -m exasol.error --help`.

### Parsing the error definitions in a python file(s)

```shell
ec parse some-python-file.py
```

```shell
ec parse < some-python-file.py
```

## Generating an error-code data file

In order to generate a [error-code-report](https://schemas.exasol.com/error_code_report-1.0.0.json) compliant data file,
you can use the generate subcommand.

```shell
ec generate NAME VERSION PACKAGE_ROOT > error-codes.json
```

### Information for Users

* [User Guide](doc/user_guide/user_guide.md)
Expand Down
3 changes: 3 additions & 0 deletions doc/changes/changes_0.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Code Name: T.B.D

T.B.D

## Feature
- #4: Add error parser/crawler cli tool

### Refactoring

- #19: Rework error reporting API
Expand Down
4 changes: 4 additions & 0 deletions exasol/error/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from exasol.error._cli import main

if __name__ == "__main__":
main()
148 changes: 148 additions & 0 deletions exasol/error/_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import argparse
import json
import sys
from enum import IntEnum
from itertools import chain
from pathlib import Path

from exasol.error._parse import parse_file
from exasol.error._report import JsonEncoder


class ExitCode(IntEnum):
SUCCESS = 0
FAILURE = -1


def parse_command(args: argparse.Namespace) -> ExitCode:
"""Parse errors out one or more python files and report them in the jsonl format."""
for f in args.python_file:
definitions, warnings, errors = parse_file(f)
for d in definitions:
print(json.dumps(d, cls=JsonEncoder))
for w in warnings:
print(w, file=sys.stderr)
if errors:
print("\n".join(str(e) for e in errors), file=sys.stderr)
return ExitCode.FAILURE

return ExitCode.SUCCESS


def generate_command(args: argparse.Namespace) -> ExitCode:
"""Generate an error code file for the specified workspace
TODO: Improve command by reflecting information from pyproject.toml file
* Derive python files from pyproject.toml package definition
* Derive name form pyproject.toml
* Derive version from pyproject.toml
see also [Github Issue #24](https://github.com/exasol/error-reporting-python/issues/24)
"""

def _report(project_name, project_version, errors):
return {
"$schema": "https://schemas.exasol.com/error_code_report-1.0.0.json",
"projectName": project_name,
"projectVersion": project_version,
"errorCodes": [e for e in errors],
}

all_definitions = list()
all_warnings = list()
paths = [Path(p) for p in args.root]
files = {f for f in chain.from_iterable([root.glob("**/*.py") for root in paths])}
for f in files:
definitions, warnings, errors = parse_file(f)

if errors:
print("\n".join(str(e) for e in errors), file=sys.stderr)
return ExitCode.FAILURE

all_definitions.extend(definitions)
all_warnings.extend(warnings)

for w in all_warnings:
print(w, file=sys.stderr)
error_catalogue = _report(args.name, args.version, all_definitions)
print(json.dumps(error_catalogue, cls=JsonEncoder))

return ExitCode.SUCCESS


def _argument_parser():
parser = argparse.ArgumentParser(
prog="ec",
description="Error Crawler",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--debug", action="store_true", help="Do not protect main entry point."
)
subparsers = parser.add_subparsers()

parse = subparsers.add_parser(
name="parse",
description="parses error definitions out of python files and reports them in jsonl format",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parse.add_argument(
"python_file",
metavar="python-file",
type=argparse.FileType("r"),
default=[sys.stdin],
nargs="*",
help="file to parse",
)
parser.set_defaults(func=parse_command)

generate = subparsers.add_parser(
name="generate",
description="Generate an error code report for a project.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
generate.add_argument(
"name",
metavar="name",
type=str,
help="of the project which will be in the report",
)
generate.add_argument(
"version",
metavar="version",
type=str,
help="which shall be in the report. Should be in the following format 'MAJOR.MINOR.PATCH'",
)
generate.add_argument(
"root",
metavar="root",
type=Path,
default=[Path(".")],
nargs="*",
help="to start recursively fetching python files from",
)
generate.set_defaults(func=generate_command)

return parser


def main():
parser = _argument_parser()
args = parser.parse_args()

def _unprotected(func, *args, **kwargs):
sys.exit(func(*args, **kwargs))

def _protected(func, *args, **kwargs):
try:
sys.exit(func(*args, **kwargs))
except Exception as ex:
print(
f"Error occurred, details: {ex}. Try running with --debug to get more details."
)
sys.exit(ExitCode.FAILURE)

if args.debug:
_unprotected(args.func, args)
else:
_protected(args.func, args)
4 changes: 2 additions & 2 deletions exasol/error/_error.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import warnings
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from typing import Dict, List, Union
from typing import Dict, Iterable, List, Mapping, Union

with warnings.catch_warnings():
warnings.simplefilter("ignore")
Expand Down Expand Up @@ -78,6 +77,7 @@ def ExaError(
FIXME: Due to legacy reasons this function currently still may raise an `ValueError` (Refactoring Required).
Potential error scenarios which should taken into account are the following ones:
* E-ERP-1: Invalid error code provided
params:
* Original ErrorCode
Expand Down
Loading

0 comments on commit f10b253

Please sign in to comment.