Skip to content

Commit baef774

Browse files
Add --project-key CLI option to usethis show sonarqube (#1487)
* Initial plan * Add --project-key CLI option to `usethis show sonarqube` command Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com> Agent-Logs-Url: https://github.com/usethis-python/usethis-python/sessions/e9843cd4-603e-4310-92b6-6b614347f0c0 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
1 parent 16a2b2b commit baef774

5 files changed

Lines changed: 132 additions & 5 deletions

File tree

src/usethis/_core/show.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ def show_name() -> None:
1414
plain_print(get_project_name())
1515

1616

17-
def show_sonarqube_config() -> None:
18-
plain_print(get_sonar_project_properties())
17+
def show_sonarqube_config(*, project_key: str | None = None) -> None:
18+
plain_print(get_sonar_project_properties(project_key=project_key))

src/usethis/_integrations/sonarqube/config.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from usethis._python.version import PythonVersion, PythonVersionParseError
1616

1717

18-
def get_sonar_project_properties() -> str:
18+
def get_sonar_project_properties(*, project_key: str | None = None) -> str:
1919
"""Get contents for (or from) the sonar-project.properties file."""
2020
path = usethis_config.cpd() / "sonar-project.properties"
2121
if path.exists() and path.is_file():
@@ -31,7 +31,10 @@ def get_sonar_project_properties() -> str:
3131
except (FileNotFoundError, PythonVersionParseError):
3232
python_version = PythonVersion.from_interpreter().to_short_string()
3333

34-
project_key = _get_sonarqube_project_key()
34+
if project_key is not None:
35+
_validate_project_key(project_key)
36+
else:
37+
project_key = _get_sonarqube_project_key()
3538
verbose = _is_sonarqube_verbose()
3639
exclusions = _get_sonarqube_exclusions()
3740

src/usethis/_ui/interface/show.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
help="Show information about the current project.", add_completion=False
88
)
99

10+
# show sonarqube options
11+
project_key_opt = typer.Option(
12+
None,
13+
"--project-key",
14+
help="SonarQube project key. If not provided, will be read from 'tool.usethis.sonarqube.project-key' in 'pyproject.toml'.",
15+
)
16+
1017

1118
@app.command(help="Show the inferred project manager backend, e.g. 'uv' or 'none'.")
1219
def backend(
@@ -51,6 +58,7 @@ def name(
5158
def sonarqube(
5259
offline: bool = offline_opt,
5360
quiet: bool = quiet_opt,
61+
project_key: str | None = project_key_opt,
5462
) -> None:
5563
from usethis._config_file import files_manager
5664
from usethis._console import err_print
@@ -59,7 +67,7 @@ def sonarqube(
5967

6068
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
6169
try:
62-
show_sonarqube_config()
70+
show_sonarqube_config(project_key=project_key)
6371
except UsethisError as err:
6472
err_print(err)
6573
raise typer.Exit(code=1) from None

tests/usethis/_integrations/sonarqube/test_sonarqube_config.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,65 @@ def test_missing_coverage_file_location_error(self, tmp_path: Path):
336336
with pytest.raises(CoverageReportConfigNotFoundError):
337337
get_sonar_project_properties()
338338

339+
def test_project_key_argument(self, tmp_path: Path):
340+
with change_cwd(tmp_path), PyprojectTOMLManager():
341+
# Arrange
342+
uv_python_pin("3.12")
343+
ensure_pyproject_toml()
344+
PyprojectTOMLManager().set_value(
345+
keys=["tool", "coverage", "xml", "output"], value="coverage.xml"
346+
)
347+
348+
# Act
349+
result = get_sonar_project_properties(project_key="cli-key")
350+
351+
# Assert
352+
assert (
353+
result
354+
== """\
355+
sonar.projectKey=cli-key
356+
sonar.language=py
357+
sonar.python.version=3.12
358+
sonar.sources=./
359+
sonar.tests=./tests
360+
sonar.python.coverage.reportPaths=coverage.xml
361+
sonar.verbose=false
362+
sonar.exclusions=tests/*
363+
"""
364+
)
365+
366+
def test_project_key_argument_overrides_pyproject(self, tmp_path: Path):
367+
with change_cwd(tmp_path), PyprojectTOMLManager():
368+
# Arrange
369+
uv_python_pin("3.12")
370+
ensure_pyproject_toml()
371+
PyprojectTOMLManager().set_value(
372+
keys=["tool", "usethis", "sonarqube", "project-key"],
373+
value="from-pyproject",
374+
)
375+
PyprojectTOMLManager().set_value(
376+
keys=["tool", "coverage", "xml", "output"], value="coverage.xml"
377+
)
378+
379+
# Act
380+
result = get_sonar_project_properties(project_key="from-cli")
381+
382+
# Assert
383+
assert "sonar.projectKey=from-cli\n" in result
384+
385+
def test_project_key_argument_invalid(self, tmp_path: Path):
386+
with change_cwd(tmp_path), PyprojectTOMLManager():
387+
# Arrange
388+
uv_python_pin("3.12")
389+
ensure_pyproject_toml()
390+
PyprojectTOMLManager().set_value(
391+
keys=["tool", "coverage", "xml", "output"], value="coverage.xml"
392+
)
393+
394+
# Act, Assert
395+
with pytest.raises(InvalidSonarQubeProjectKeyError):
396+
get_sonar_project_properties(project_key="invalid key!")
397+
339398
def test_flat_layout_exclusions_already_has_tests(self, tmp_path: Path):
340399
# When using flat layout and tests/* is already in exclusions,
341400
# it should not be added again.

tests/usethis/_ui/interface/test_show.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,63 @@ def test_missing_key(self, tmp_path: Path):
9494
# Assert
9595
assert result.exit_code == 1, result.output
9696

97+
def test_project_key_option(self, tmp_path: Path):
98+
# Arrange
99+
(tmp_path / "pyproject.toml").write_text(
100+
"""
101+
[tool.coverage.xml.output]
102+
"""
103+
)
104+
105+
# Act
106+
runner = CliRunner()
107+
with change_cwd(tmp_path):
108+
result = runner.invoke_safe(
109+
app, ["sonarqube", "--project-key", "my-project"]
110+
)
111+
112+
# Assert
113+
assert result.exit_code == 0, result.output
114+
assert "sonar.projectKey=my-project" in result.output
115+
116+
def test_project_key_option_overrides_pyproject(self, tmp_path: Path):
117+
# Arrange
118+
(tmp_path / "pyproject.toml").write_text(
119+
"""
120+
[tool.usethis.sonarqube]
121+
project-key = "from-pyproject"
122+
123+
[tool.coverage.xml.output]
124+
"""
125+
)
126+
127+
# Act
128+
runner = CliRunner()
129+
with change_cwd(tmp_path):
130+
result = runner.invoke_safe(app, ["sonarqube", "--project-key", "from-cli"])
131+
132+
# Assert
133+
assert result.exit_code == 0, result.output
134+
assert "sonar.projectKey=from-cli" in result.output
135+
136+
def test_project_key_option_invalid(self, tmp_path: Path):
137+
# Arrange
138+
(tmp_path / "pyproject.toml").write_text(
139+
"""
140+
[tool.coverage.xml.output]
141+
"""
142+
)
143+
144+
# Act
145+
runner = CliRunner()
146+
with change_cwd(tmp_path):
147+
result = runner.invoke_safe(
148+
app, ["sonarqube", "--project-key", "invalid key!"]
149+
)
150+
151+
# Assert
152+
assert result.exit_code == 1, result.output
153+
97154
def test_invalid_pyproject(self, tmp_path: Path):
98155
# Arrange
99156
(tmp_path / "pyproject.toml").write_text("[")

0 commit comments

Comments
 (0)