Skip to content

Commit 3a30c55

Browse files
Handle missing project.version automatically (#301)
* Handle missing `project.version` automatically * Don't try to ensure validity when the file doesn't exist * Refactor to use an IO manager for pyproject.toml to prevent excessive reads * Fix test config for CI-only test * Fix whitespace issue * Add tests for ensure_pyproject_validity * Add tests for pyproject IO * Add tests for the badge interface * Add tests for pytest-bitbucket integration * Add tests for `TestGetStepsInDefault` * Test the usethis show interfaces * Fix incorrectly hard-coded version * Restructure the app definitions into their own module * Test requirements txt interface
1 parent 0c6fb50 commit 3a30c55

63 files changed

Lines changed: 1882 additions & 504 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ scripts.usethis = "usethis.__main__:app"
5050
dev = [
5151
"datamodel-code-generator[http]>=0.26.5",
5252
"deptry>=0.23.0",
53-
"import-linter>=2.1",
53+
"import-linter>=2.2",
5454
"pre-commit>=4.1.0",
55-
"pyright>=1.1.393",
56-
"ruff>=0.9.4",
55+
"pyright>=1.1.394",
56+
"ruff>=0.9.6",
5757
]
5858
test = [
5959
"coverage[toml]>=7.6.10",
@@ -149,6 +149,7 @@ type = "layers"
149149
layers = [
150150
"_test",
151151
"__main__",
152+
"_app",
152153
"_interface",
153154
"_core",
154155
"_tool | _ci",
@@ -162,13 +163,22 @@ containers = [ "usethis" ]
162163
exhaustive = true
163164
exhaustive_ignores = [ "_version" ]
164165

166+
[[tool.importlinter.contracts]]
167+
name = "Interface Independence"
168+
type = "layers"
169+
layers = [
170+
"badge | browse | ci | readme | show | tool | version",
171+
]
172+
containers = [ "usethis._interface" ]
173+
exhaustive = true
174+
165175
[[tool.importlinter.contracts]]
166176
name = "Integrations Modular Design"
167177
type = "layers"
168178
layers = [
169179
"bitbucket | github | pre_commit | pytest | ruff",
170180
"uv | pydantic | sonarqube",
171-
"pyproject | yaml",
181+
"pyproject | yaml | python",
172182
]
173183
containers = [ "usethis._integrations" ]
174184
exhaustive = true

src/usethis/__main__.py

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,7 @@
1-
"""The Typer application for usethis."""
2-
3-
import sys
4-
5-
import typer
6-
7-
import usethis._interface.badge
8-
import usethis._interface.browse
9-
import usethis._interface.ci
10-
import usethis._interface.show
11-
import usethis._interface.tool
12-
from usethis._config import quiet_opt, usethis_config
13-
from usethis._core.badge import add_pre_commit_badge, add_ruff_badge
14-
from usethis._core.readme import add_readme
15-
from usethis._tool import PreCommitTool, RuffTool
16-
17-
try:
18-
from usethis._version import __version__
19-
except ImportError:
20-
__version__ = None
21-
22-
app = typer.Typer(
23-
help=(
24-
"Automate Python package and project setup tasks that are otherwise "
25-
"performed manually."
26-
)
27-
)
28-
app.add_typer(usethis._interface.badge.app, name="badge")
29-
app.add_typer(usethis._interface.browse.app, name="browse")
30-
app.add_typer(usethis._interface.ci.app, name="ci")
31-
app.add_typer(usethis._interface.show.app, name="show")
32-
app.add_typer(usethis._interface.tool.app, name="tool")
33-
34-
35-
@app.command(help="Add a README.md file to the project.")
36-
def readme(
37-
quiet: bool = quiet_opt,
38-
badges: bool = typer.Option(False, "--badges", help="Add relevant badges"),
39-
) -> None:
40-
with usethis_config.set(quiet=quiet):
41-
add_readme()
42-
43-
if badges:
44-
if RuffTool().is_used():
45-
add_ruff_badge()
46-
47-
if PreCommitTool().is_used():
48-
add_pre_commit_badge()
49-
50-
51-
@app.command(help="Display the version of usethis.")
52-
def version() -> None:
53-
if __version__ is not None:
54-
print(__version__)
55-
else:
56-
sys.exit(1)
1+
"""The CLI application for usethis."""
572

3+
from usethis._app import app
584

595
app(prog_name="usethis")
606

61-
627
__all__ = ["app"]

src/usethis/_app.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""The Typer application for usethis."""
2+
3+
import typer
4+
5+
import usethis._interface.badge
6+
import usethis._interface.browse
7+
import usethis._interface.ci
8+
import usethis._interface.readme
9+
import usethis._interface.show
10+
import usethis._interface.tool
11+
import usethis._interface.version
12+
13+
app = typer.Typer(
14+
help=(
15+
"Automate Python package and project setup tasks that are otherwise "
16+
"performed manually."
17+
)
18+
)
19+
app.add_typer(usethis._interface.badge.app, name="badge")
20+
app.add_typer(usethis._interface.browse.app, name="browse")
21+
app.add_typer(usethis._interface.ci.app, name="ci")
22+
app.command(help="Add a README.md file to the project.")(
23+
usethis._interface.readme.readme,
24+
)
25+
app.add_typer(usethis._interface.show.app, name="show")
26+
app.add_typer(usethis._interface.tool.app, name="tool")
27+
app.command(help="Display the version of usethis.")(
28+
usethis._interface.version.version,
29+
)

src/usethis/_ci.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ def is_bitbucket_used() -> bool:
1616

1717

1818
def update_bitbucket_pytest_steps() -> None:
19-
matrix = get_supported_major_python_versions()
20-
for version in matrix:
19+
versions = get_supported_major_python_versions()
20+
21+
for version in versions:
2122
add_bitbucket_step_in_default(
2223
Step(
2324
name=f"Test on 3.{version}",
@@ -36,7 +37,7 @@ def update_bitbucket_pytest_steps() -> None:
3637
match = re.match(r"^Test on 3\.(\d+)$", step.name)
3738
if match:
3839
version = int(match.group(1))
39-
if version not in matrix:
40+
if version not in versions:
4041
remove_bitbucket_step_from_default(step)
4142

4243

src/usethis/_core/badge.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
2-
import sys
32
from pathlib import Path
43

4+
import typer
55
from pydantic import BaseModel
66
from typing_extensions import Self
77

@@ -92,7 +92,7 @@ def add_badge(badge: Badge) -> None:
9292
path = _get_markdown_readme_path()
9393
except FileNotFoundError as err:
9494
err_print(err)
95-
sys.exit(1)
95+
raise typer.Exit(code=1)
9696

9797
prerequisites: list[Badge] = []
9898
for _b in get_badge_order():

src/usethis/_core/show.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import sys
1+
import typer
22

3+
from usethis._config import usethis_config
34
from usethis._console import err_print
45
from usethis._integrations.pyproject.name import get_name
56
from usethis._integrations.sonarqube.config import get_sonar_project_properties
@@ -8,14 +9,16 @@
89

910

1011
def show_name() -> None:
11-
ensure_pyproject_toml()
12+
with usethis_config.set(quiet=True):
13+
ensure_pyproject_toml()
1214
print(get_name())
1315

1416

1517
def show_sonarqube_config() -> None:
16-
ensure_pyproject_toml()
18+
with usethis_config.set(quiet=True):
19+
ensure_pyproject_toml()
1720
try:
1821
print(get_sonar_project_properties())
1922
except UsethisError as err:
2023
err_print(err)
21-
sys.exit(1)
24+
raise typer.Exit(code=1)

src/usethis/_core/tool.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ def use_requirements_txt(*, remove: bool = False) -> None:
313313
# N.B. this is where a task runner would come in handy, to reduce duplication.
314314
if not (Path.cwd() / "uv.lock").exists() and not usethis_config.frozen:
315315
tick_print("Writing 'uv.lock'.")
316-
call_uv_subprocess(["lock"])
316+
call_uv_subprocess(["lock"], change_toml=False)
317317

318318
if not usethis_config.frozen:
319319
tick_print("Writing 'requirements.txt'.")
@@ -323,7 +323,8 @@ def use_requirements_txt(*, remove: bool = False) -> None:
323323
"--frozen",
324324
"--no-dev",
325325
"--output-file=requirements.txt",
326-
]
326+
],
327+
change_toml=False,
327328
)
328329

329330
if not is_pre_commit:

src/usethis/_integrations/bitbucket/steps.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,9 @@ def _add_step_in_default_via_doc(
101101
) -> None:
102102
_add_step_caches_via_doc(step, doc=doc)
103103

104-
if step.name == _PLACEHOLDER_NAME:
105-
pass # We need to selectively choose to report at a higher level.
104+
if step.name != _PLACEHOLDER_NAME:
105+
# We need to selectively choose to report at a higher level.
106106
# It's not always notable that the placeholder is being added.
107-
else:
108107
tick_print(
109108
f"Adding '{step.name}' to default pipeline in 'bitbucket-pipelines.yml'."
110109
)
@@ -157,14 +156,15 @@ def _add_step_in_default_via_doc(
157156
# N.B. Currently, we are not accounting for parallelism, whereas all these steps
158157
# could be parallel potentially.
159158
# See https://github.com/nathanjmcdougall/usethis-python/issues/149
159+
maj_versions = get_supported_major_python_versions()
160160
step_order = [
161161
"Run pre-commit",
162162
# For these tools, sync them with the pre-commit removal logic
163163
"Run pyproject-fmt",
164164
"Run Ruff",
165165
"Run Deptry",
166166
"Run Codespell",
167-
*[f"Test on 3.{maj}" for maj in get_supported_major_python_versions()],
167+
*[f"Test on 3.{maj_version}" for maj_version in maj_versions],
168168
]
169169
for step_name in step_order:
170170
if step_name == step.name:

src/usethis/_integrations/pre_commit/core.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def install_pre_commit_hooks() -> None:
2929

3030
tick_print("Ensuring pre-commit is installed to Git.")
3131
try:
32-
call_uv_subprocess(["run", "pre-commit", "install"])
32+
call_uv_subprocess(["run", "pre-commit", "install"], change_toml=False)
3333
except UVSubprocessFailedError as err:
3434
msg = f"Failed to install pre-commit in the Git repository:\n{err}"
3535
raise PreCommitInstallationError(msg) from None
@@ -38,7 +38,7 @@ def install_pre_commit_hooks() -> None:
3838
"This may take a minute or so while the hooks are downloaded.", temporary=True
3939
)
4040
try:
41-
call_uv_subprocess(["run", "pre-commit", "install-hooks"])
41+
call_uv_subprocess(["run", "pre-commit", "install-hooks"], change_toml=False)
4242
except UVSubprocessFailedError as err:
4343
msg = f"Failed to install pre-commit hooks:\n{err}"
4444
raise PreCommitInstallationError(msg) from None
@@ -57,7 +57,10 @@ def uninstall_pre_commit_hooks() -> None:
5757

5858
tick_print("Ensuring pre-commit hooks are uninstalled.")
5959
try:
60-
call_uv_subprocess(["run", "--with", "pre-commit", "pre-commit", "uninstall"])
60+
call_uv_subprocess(
61+
["run", "--with", "pre-commit", "pre-commit", "uninstall"],
62+
change_toml=False,
63+
)
6164
except UVSubprocessFailedError as err:
6265
msg = f"Failed to uninstall pre-commit hooks:\n{err}"
6366
raise PreCommitInstallationError(msg) from None

src/usethis/_integrations/pyproject/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def set_config_value(
4646

4747
try:
4848
# Index our way into each ID key.
49-
# Eventually, we should land at a final dict, which si the one we are setting.
49+
# Eventually, we should land at a final dict, which is the one we are setting.
5050
p, parent = pyproject, {}
5151
for key in id_keys:
5252
TypeAdapter(dict).validate_python(p)

0 commit comments

Comments
 (0)