pyright-to-gitlab 1.1.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyright-to-gitlab
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: Convert Pyright JSON output to GitLab CI/CD format
5
5
  Project-URL: homepage, https://github.com/schollm/pyright-to-gitlab/
6
6
  Project-URL: repository, https://github.com/schollm/pyright-to-gitlab/
@@ -12,7 +12,6 @@ Keywords: ci/cd,gitlab,pyright
12
12
  Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3.7
16
15
  Classifier: Programming Language :: Python :: 3.8
17
16
  Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
@@ -24,12 +23,21 @@ Classifier: Topic :: Software Development
24
23
  Classifier: Topic :: Software Development :: Testing
25
24
  Classifier: Topic :: Utilities
26
25
  Classifier: Typing :: Typed
27
- Requires-Python: >=3.6
26
+ Requires-Python: >=3.8
28
27
  Description-Content-Type: text/markdown
29
28
 
30
29
  # pyright to gitlab
31
- Simple zero-dependency Python script to convert a pyright --outputjson to a gitlab
32
- code-quality report.
30
+
31
+ [![PyPI version](https://img.shields.io/pypi/v/pyright-to-gitlab.svg)](https://pypi.org/project/pyright-to-gitlab)
32
+ [![Python versions](https://img.shields.io/pypi/pyversions/pyright-to-gitlab.svg)](https://pypi.org/project/pyright-to-gitlab)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
35
+ [![Typing: typed](https://img.shields.io/badge/typing-typed-blue.svg)](https://github.com/python/mypy)
36
+
37
+ Simple Python tool to convert Pyright JSON output to GitLab Code Quality report format.
38
+
39
+ **Zero runtime dependencies** - Pure Python implementation using only the standard library.
40
+
33
41
 
34
42
  ## Usage
35
43
  Run the script with the path to the pyright output file:
@@ -44,12 +52,12 @@ $ pip install pyright-to-gitlab
44
52
  $ pyright . --outputjson | python -m pyright_to_gitlab > code-quality.json
45
53
  ```
46
54
  ### Custom path prefix
47
- The `--prefix` option adds a custom prefix to the file paths in the output. This is
48
- useful if the paths in the pyright output are not relative to the root of the repository.
55
+ The `--prefix` option adds a custom prefix path to the file paths in the output. This is
56
+ useful for mono-repos, where the paths in the pyright output is not the repository root.
49
57
 
50
58
 
51
59
  ```shell
52
- $ pyright . --outputjson | pyright-to-gitlab --prefix my-app/ > code-quality.json
60
+ $ pyright . --outputjson | pyright-to-gitlab --prefix my-app > code-quality.json
53
61
  ```
54
62
 
55
63
  ## Testing
@@ -0,0 +1,6 @@
1
+ pyright_to_gitlab.py,sha256=qqTJVAQ9OS44gj9L4KPpWDTliKNP39vEe27SZfxW_zY,6289
2
+ pyright_to_gitlab-1.3.0.dist-info/METADATA,sha256=Q63XdhBtllH0kPKRUdcul0Y2jUHZVuQSm9AY7P_iDBs,2634
3
+ pyright_to_gitlab-1.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
4
+ pyright_to_gitlab-1.3.0.dist-info/entry_points.txt,sha256=0RnHrvYlZf3zD9BRTVQL564gF_fSuwF5IP13OB18540,61
5
+ pyright_to_gitlab-1.3.0.dist-info/licenses/LICENSE,sha256=tY29-MdtyUNoC3XN4IBzu_jxmb8U82z9UvLkQJuBiJM,1070
6
+ pyright_to_gitlab-1.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyright-to-gitlab = pyright_to_gitlab:main
pyright_to_gitlab.py CHANGED
@@ -1,53 +1,162 @@
1
1
  """Convert pyright.json output to GitLab Code Quality report format."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import argparse
4
6
  import hashlib
5
7
  import json
6
8
  import sys
7
9
  import textwrap
8
- from typing import TextIO, cast
10
+ from typing import Literal, TextIO, TypedDict
11
+
12
+
13
+ ### Typing for PyRight Issue
14
+ class PyrightRangeElement(TypedDict, total=False):
15
+ """Pyright Range Element (part of Range)."""
16
+
17
+ line: int
18
+ character: int
19
+
20
+
21
+ class PyrightRange(TypedDict, total=False):
22
+ """Pyright Range (Part of Issue)."""
23
+
24
+ start: PyrightRangeElement
25
+ end: PyrightRangeElement
26
+
27
+
28
+ class PyrightIssue(TypedDict, total=False):
29
+ """Single Pyright Issue.
30
+
31
+ Note: total=False makes all fields optional. Runtime code handles this with
32
+ defensive .get() calls.
33
+ """
34
+
35
+ file: str
36
+ severity: Literal["error", "warning", "information"]
37
+ message: str
38
+ rule: str # Optional in practice, handled via .get() at runtime
39
+ range: PyrightRange
40
+
41
+
42
+ ### Typing for Gitlab Issue
43
+ class GitlabIssuePositionLocation(TypedDict):
44
+ """Single Gitlab Position (Part of Position)."""
45
+
46
+ line: int
47
+ column: int
48
+
49
+
50
+ class GitlabIssuePositions(TypedDict):
51
+ """Gitlab ranged Position within a file (Part of Location)."""
52
+
53
+ begin: GitlabIssuePositionLocation
54
+ end: GitlabIssuePositionLocation
55
+
56
+
57
+ class GitlabIssueLocation(TypedDict):
58
+ """Gitlab location (Part of Issue)."""
59
+
60
+ path: str
61
+ positions: GitlabIssuePositions
62
+
9
63
 
64
+ class GitlabIssue(TypedDict):
65
+ """Single Gitlab Issue."""
10
66
 
67
+ description: str
68
+ severity: Literal["major", "minor"]
69
+ fingerprint: str
70
+ check_name: str
71
+ location: GitlabIssueLocation
72
+
73
+
74
+ ### Functions
11
75
  def _pyright_to_gitlab(input_: TextIO, prefix: str = "") -> str:
12
76
  """Convert pyright.json output to GitLab Code Quality report format.
13
77
 
14
- :arg prefix: A string to prepend to each file path in the output.
78
+ Line numbers from Pyright are passed through unchanged (0-based per LSP spec).
79
+ GitLab expects the same format, so no conversion is needed.
80
+
81
+ :arg prefix: A path to prepend to each file path in the output.
15
82
  This is useful if the application is in a subdirectory of the repository.
16
83
  :return: JSON of issues in GitLab Code Quality report format.
84
+ :raises ValueError: If input is not a JSON object.
85
+ :raises TypeError: If input JSON is not an object.
17
86
 
18
87
  Pyright format at https://github.com/microsoft/pyright/blob/main/docs/command-line.md
19
88
  Gitlab format at https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
20
89
  """
21
- data = cast("dict", json.load(input_))
22
-
23
- issues = []
24
- for issue in data.get("generalDiagnostics", []):
25
- file = issue["file"]
26
- start, end = issue["range"]["start"], issue["range"]["end"]
27
- rule = "pyright: " + issue.get("rule", "")
28
- severity = "major" if issue["severity"] == "error" else "minor"
29
- # unique fingerprint
30
- fp_str = "--".join([str(start), str(end), rule])
31
-
32
- issues.append(
33
- {
34
- "description": issue["message"],
35
- "severity": severity,
36
- "fingerprint": hashlib.sha3_224(fp_str.encode()).hexdigest(),
37
- "check_name": rule,
38
- "location": {
39
- "path": f"{prefix}{file}",
40
- "positions": {
41
- "begin": {"line": start["line"], "column": start["character"]},
42
- "end": {"line": end["line"], "column": end["character"]},
43
- },
44
- },
45
- }
46
- )
47
- return json.dumps(issues, indent=2)
48
-
49
-
50
- def main() -> None:
90
+ if prefix and not prefix.endswith("/"):
91
+ prefix += "/"
92
+
93
+ try:
94
+ data = json.load(input_)
95
+ except json.JSONDecodeError as e:
96
+ err_msg = f"Invalid JSON input: {e}"
97
+ raise ValueError(err_msg) from e
98
+
99
+ if not isinstance(data, dict):
100
+ err_msg = "Input must be a JSON object"
101
+ raise TypeError(err_msg)
102
+
103
+ return json.dumps(
104
+ [
105
+ _pyright_issue_to_gitlab(issue, prefix)
106
+ for issue in data.get("generalDiagnostics", [])
107
+ ],
108
+ indent=2,
109
+ )
110
+
111
+
112
+ def _pyright_issue_to_gitlab(issue: PyrightIssue, prefix: str) -> GitlabIssue:
113
+ """Convert a single issue to gitlab.
114
+
115
+ Uses defensive .get() with defaults to handle missing optional fields.
116
+ File path defaults to '<anonymous>' and missing rule to 'unknown'.
117
+
118
+ :param issue: A pyright single issue.
119
+ :param prefix: The path prefix.
120
+ :returns: A gitlab single issue.
121
+ """
122
+ range_ = issue.get("range", {})
123
+ start, end = (range_.get("start", {}), range_.get("end", {}))
124
+ rule = "pyright: " + issue.get("rule", "unknown")
125
+ # Hash input must contain file, location and rule to generate a unique fingerprint.
126
+ # (This takes advantage of stable dict order).
127
+ fingerprint = f"{issue.get('file', '<anonymous>')}--{range_}--{rule}"
128
+
129
+ return GitlabIssue(
130
+ description=issue.get("message", ""),
131
+ # Map 'error' to 'major', all others, including empty, to 'minor'
132
+ severity="major" if issue.get("severity") == "error" else "minor",
133
+ # Any hash function really works, does not have to be cryptographic.
134
+ fingerprint=_hash(fingerprint),
135
+ check_name=rule,
136
+ location=GitlabIssueLocation(
137
+ path=f"{prefix}{issue.get('file', '<anonymous>')}",
138
+ positions=GitlabIssuePositions(
139
+ begin=GitlabIssuePositionLocation(
140
+ line=start.get("line", 0), column=start.get("character", 0)
141
+ ),
142
+ end=GitlabIssuePositionLocation(
143
+ line=end.get("line", 0), column=end.get("character", 0)
144
+ ),
145
+ ),
146
+ ),
147
+ )
148
+
149
+
150
+ def _hash(data: str) -> str:
151
+ """Generate an (non-secure) hash of the given data string.
152
+
153
+ :param data: The input string to hash.
154
+ :returns: The hexadecimal representation of the MD5 hash.
155
+ """
156
+ return hashlib.new("md5", data.encode(), usedforsecurity=False).hexdigest()
157
+
158
+
159
+ def cli() -> None:
51
160
  """Parse arguments and call the conversion function."""
52
161
  parser = argparse.ArgumentParser(
53
162
  description=textwrap.dedent("""
@@ -81,12 +190,13 @@ def main() -> None:
81
190
  "--prefix",
82
191
  type=str,
83
192
  default="",
84
- help="Prefix to add to each file entry. This can be used if pyright is run"
193
+ help="Prefix path to add to each file entry. This can be used if pyright is run"
85
194
  " from a subdirectory of the repository. (default: empty string)",
86
195
  )
196
+ parser.add_argument("--version", action="version", version="%(prog)s 1.3.0")
87
197
  args = parser.parse_args()
88
198
  args.output.write(_pyright_to_gitlab(input_=args.input, prefix=args.prefix))
89
199
 
90
200
 
91
201
  if __name__ == "__main__": # pragma: no cover
92
- main()
202
+ cli()
@@ -1,6 +0,0 @@
1
- pyright_to_gitlab.py,sha256=i_54FlXg4RD1_1dr5Kq6onIAua2zRsi9hs_uejB_SK0,3150
2
- pyright_to_gitlab-1.1.0.dist-info/METADATA,sha256=9WGRB8rUH9ylhs98la_TMihlgQ6VNBe_gTAtrFOGjJk,2034
3
- pyright_to_gitlab-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
- pyright_to_gitlab-1.1.0.dist-info/entry_points.txt,sha256=MbNff91bPuO2XhW0P03N9O1Z7xtlr3x9duU5BftRbZo,60
5
- pyright_to_gitlab-1.1.0.dist-info/licenses/LICENSE,sha256=tY29-MdtyUNoC3XN4IBzu_jxmb8U82z9UvLkQJuBiJM,1070
6
- pyright_to_gitlab-1.1.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- pyrigh-to-gitlab = pyright_to_gitlab:main