Browse Source

ref: prevent adding an ignored module to the typing stronglist (#68824)

<!-- Describe your PR here. -->
anthony sottile 11 months ago
parent
commit
4fcbd74db6

+ 5 - 0
.pre-commit-config.yaml

@@ -87,6 +87,11 @@ repos:
         entry: python3 -m tools.mypy_helpers.sort_stronger_modules
         files: ^pyproject\.toml$
         language: python
+      - id: check-mypy-stronglist
+        name: check mypy stronglist
+        entry: python3 -m tools.mypy_helpers.check_stronglist
+        files: ^pyproject\.toml$
+        language: python
       - id: prevent-push
         name: prevent pushing master
         stages: [pre-push]

+ 0 - 2
pyproject.toml

@@ -592,7 +592,6 @@ module = [
     "sentry.buffer.base",
     "sentry.buffer.redis",
     "sentry.eventstore.reprocessing.redis",
-    "sentry.grouping.fingerprinting",
     "sentry.issues.related.*",
     "sentry.lang.java.processing",
     "sentry.migrations.*",
@@ -605,7 +604,6 @@ module = [
     "sentry.relay.config.metric_extraction",
     "sentry.reprocessing2",
     "sentry.runner.*",
-    "sentry.search.events.datasets.*",
     "sentry.snuba.metrics.extraction",
     "sentry.tasks.commit_context",
     "sentry.tasks.on_demand_metrics",

+ 78 - 0
tests/tools/mypy_helpers/test_check_mypy_stronglist.py

@@ -0,0 +1,78 @@
+import re
+
+from tools.mypy_helpers.check_stronglist import _glob_to_re, main
+
+
+def test_glob_to_re_exact_matches():
+    pat = re.compile(_glob_to_re("a.b.c"))
+    assert pat.fullmatch("a.b.c")
+    assert not pat.fullmatch("a.b.c.d")
+    assert not pat.fullmatch("a_b_c")
+
+
+def test_glob_to_re_wildcards():
+    pat = re.compile(_glob_to_re("a.b.c.*"))
+    assert pat.fullmatch("a.b.c")
+    assert pat.fullmatch("a.b.c.d")
+    assert not pat.fullmatch("a_b_c")
+
+
+def test_ok(tmp_path):
+    src = """\
+[[tool.mypy.overrides]]
+module = ["a.b.c", "d.e.f", "g.h.i"]
+disable_error_code = ["misc"]
+
+[[tool.mypy.overrides]]
+module = ["j.k.*"]
+disallow_untyped_defs = true
+"""
+    f = tmp_path.joinpath("f")
+    f.write_text(src)
+
+    assert main((str(f),)) == 0
+
+
+def test_errors_on_exact_module(tmp_path, capsys):
+    src = """\
+[[tool.mypy.overrides]]
+module = ["a.b.c", "d.e.f", "g.h.i"]
+disable_error_code = ["misc"]
+
+[[tool.mypy.overrides]]
+module = ["a.b.c", "d.e.f"]
+disallow_untyped_defs = true
+"""
+    f = tmp_path.joinpath("f")
+    f.write_text(src)
+
+    assert main((str(f),)) == 1
+
+    expected = f"""\
+{f}: a.b.c is in the typing errors allowlist *and* stronglist
+{f}: d.e.f is in the typing errors allowlist *and* stronglist
+"""
+    assert capsys.readouterr().out == expected
+
+
+def test_errors_on_globbed_module(tmp_path, capsys):
+    src = """\
+[[tool.mypy.overrides]]
+module = ["a.b.c", "a.b.c.d", "a.b.c.e"]
+disable_error_code = ["misc"]
+
+[[tool.mypy.overrides]]
+module = ["a.b.c.*"]
+disallow_untyped_defs = true
+"""
+    f = tmp_path.joinpath("f")
+    f.write_text(src)
+
+    assert main((str(f),)) == 1
+
+    expected = f"""\
+{f}: a.b.c is in the typing errors allowlist *and* stronglist
+{f}: a.b.c.d is in the typing errors allowlist *and* stronglist
+{f}: a.b.c.e is in the typing errors allowlist *and* stronglist
+"""
+    assert capsys.readouterr().out == expected

+ 40 - 0
tools/mypy_helpers/check_stronglist.py

@@ -0,0 +1,40 @@
+import argparse
+import re
+from collections.abc import Sequence
+
+import tomllib
+
+
+def _glob_to_re(s: str) -> str:
+    if s.endswith(".*"):
+        pat = rf'{re.escape(s.removesuffix(".*"))}(?:|\..*+)'
+    else:
+        pat = re.escape(s)
+    return f"^{pat}$"
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("filenames", nargs="*")
+    args = parser.parse_args(argv)
+
+    retv = 0
+    for filename in args.filenames:
+        with open(filename, "rb") as f:
+            overrides = tomllib.load(f)["tool"]["mypy"]["overrides"]
+
+        (allowlist,) = (cfg for cfg in overrides if "disable_error_code" in cfg)
+        (stronglist,) = (cfg for cfg in overrides if "disallow_untyped_defs" in cfg)
+
+        stronglist_re = re.compile("|".join(_glob_to_re(g) for g in stronglist["module"]))
+
+        for mod in allowlist["module"]:
+            if stronglist_re.fullmatch(mod):
+                print(f"{filename}: {mod} is in the typing errors allowlist *and* stronglist")
+                retv = 1
+
+    return retv
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())