Skip to content

Commit a286d47

Browse files
authored
DOP-969: Add an environment variable to support emitting diagnostics in JSON (#153)
* add serialize method to diag class and env variable * mypy type inference failing? * serialize() returns SerializedNode * add test to test_main * add test for JSON option * in test, check message matches diagnostic.message attribute * test all diag attributes * lint * main print returns json objects, not json converted to str * clean up * lint * updates from CR * add docstring to serialize() * fix env vars in module docstring * change default of DIAGNOSTICS_FORMAT env var, other updates following CR
1 parent bbc95ae commit a286d47

File tree

3 files changed

+78
-20
lines changed

3 files changed

+78
-20
lines changed

snooty/diagnostics.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import enum
2-
from typing import Tuple, Union
2+
from typing import Tuple, Union, Dict
33
from pathlib import Path
4+
from .n import SerializableType
5+
from . import n
46

57

68
class Diagnostic:
@@ -46,6 +48,14 @@ def severity(self) -> "Diagnostic.Level":
4648
def severity_string(self) -> str:
4749
return self.severity.name.title()
4850

51+
def serialize(self) -> n.SerializedNode:
52+
"""Create dict containing diagnostic attributes for neatly reporting diagnostics at program completion"""
53+
diag: Dict[str, SerializableType] = {}
54+
diag["severity"] = self.severity_string.upper()
55+
diag["start"] = self.start[0]
56+
diag["message"] = self.message
57+
return diag
58+
4959

5060
class UnexpectedIndentation(Diagnostic):
5161
severity = Diagnostic.Level.error

snooty/main.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
-h --help Show this screen.
1010
--commit=<commit_hash> Commit hash of build.
1111
--patch=<patch_id> Patch ID of build. Must be specified with a commit hash.
12+
13+
Environment variables:
14+
SNOOTY_PARANOID 0, 1 where 0 is default
15+
DIAGNOSTICS_FORMAT JSON, text where text is default
16+
1217
"""
1318
import getpass
1419
import logging
1520
import os
1621
import pymongo
1722
import sys
1823
import toml
24+
import json
1925
import watchdog.events
2026
import watchdog.observers
2127
from pathlib import Path, PurePath
@@ -81,16 +87,17 @@ def on_progress(self, progress: int, total: int, message: str) -> None:
8187
pass
8288

8389
def on_diagnostics(self, path: FileId, diagnostics: List[Diagnostic]) -> None:
90+
output = os.environ.get("DIAGNOSTICS_FORMAT", "text")
91+
8492
for diagnostic in diagnostics:
85-
# Line numbers are currently... uh, "approximate"
86-
print(
87-
"{}({}:{}ish): {}".format(
88-
diagnostic.severity_string.upper(),
89-
path,
90-
diagnostic.start[0],
91-
diagnostic.message,
92-
)
93-
)
93+
info = diagnostic.serialize()
94+
info["path"] = path.as_posix()
95+
96+
if output == "JSON":
97+
document = {"diagnostic": info}
98+
print(json.dumps(document))
99+
else:
100+
print("{severity}({path}:{start}ish): {message}".format(**info))
94101
self.total_warnings += 1
95102

96103
def on_update(

snooty/test_main.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
import json
13
import builtins
24
from typing import Any, List
35
from .types import FileId
@@ -14,20 +16,59 @@ def test_print(*values: Any, **kwargs: Any) -> None:
1416
backend = main.Backend()
1517
orig_print = builtins.print
1618
builtins.print = test_print
19+
test_diagnostics = [
20+
InvalidLiteralInclude("invalid literal include error", 10, 12),
21+
InvalidURL((10, 0), (12, 30)),
22+
UnknownSubstitution("unknown substitution warning", 10),
23+
]
1724
try:
18-
backend.on_diagnostics(
19-
FileId("foo/bar.rst"),
20-
[InvalidLiteralInclude("an error", 10, 12), InvalidURL((10, 0), (12, 30))],
21-
)
22-
backend.on_diagnostics(
23-
FileId("foo/foo.rst"), [UnknownSubstitution("a warning", 10)]
24-
)
25+
backend.on_diagnostics(FileId("foo/bar.rst"), test_diagnostics[0:2])
26+
backend.on_diagnostics(FileId("foo/foo.rst"), test_diagnostics[2:])
2527
assert backend.total_warnings == 3
2628
finally:
2729
builtins.print = orig_print
2830

2931
assert messages == [
30-
"ERROR(foo/bar.rst:10ish): an error",
31-
"ERROR(foo/bar.rst:10ish): Invalid URL",
32-
"WARNING(foo/foo.rst:10ish): a warning",
32+
f"ERROR(foo/bar.rst:10ish): {test_diagnostics[0].message}",
33+
f"ERROR(foo/bar.rst:10ish): {test_diagnostics[1].message}",
34+
f"WARNING(foo/foo.rst:10ish): {test_diagnostics[2].message}",
35+
]
36+
37+
# test returning diagnostic messages as JSON
38+
backend = main.Backend()
39+
messages.clear()
40+
builtins.print = test_print
41+
os.environ["DIAGNOSTICS_FORMAT"] = "JSON"
42+
try:
43+
backend.on_diagnostics(FileId("foo/bar.rst"), test_diagnostics[0:2])
44+
backend.on_diagnostics(FileId("foo/foo.rst"), test_diagnostics[2:])
45+
assert backend.total_warnings == 3
46+
finally:
47+
builtins.print = orig_print
48+
49+
assert [json.loads(message) for message in messages] == [
50+
{
51+
"diagnostic": {
52+
"severity": "ERROR",
53+
"start": 10,
54+
"message": test_diagnostics[0].message,
55+
"path": "foo/bar.rst",
56+
}
57+
},
58+
{
59+
"diagnostic": {
60+
"severity": "ERROR",
61+
"start": 10,
62+
"message": test_diagnostics[1].message,
63+
"path": "foo/bar.rst",
64+
}
65+
},
66+
{
67+
"diagnostic": {
68+
"severity": "WARNING",
69+
"start": 10,
70+
"message": test_diagnostics[2].message,
71+
"path": "foo/foo.rst",
72+
}
73+
},
3374
]

0 commit comments

Comments
 (0)