Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7752510
Fix indentation and line length issues in docstrings in `base.py`
nathanjmcdougall Jan 8, 2026
16c5c2c
Major architectural changes to improve functionalization of pre-commi…
nathanjmcdougall Jan 24, 2026
e5f7fd8
Remove trailing `s` from comment
nathanjmcdougall Jan 24, 2026
e1a635e
Simplify placeholder check
nathanjmcdougall Jan 24, 2026
13fde34
Fix broken mock reference
nathanjmcdougall Jan 24, 2026
7a70fc9
Bump fallback versions
nathanjmcdougall Jan 24, 2026
e28b6ad
Add early return to `_unconditional_update_bitbucket_steps`
nathanjmcdougall Jan 24, 2026
c5705d7
Update copilot instructions regarding style
nathanjmcdougall Feb 8, 2026
84c37e7
Bump fallback uv version 0.9.26 -> 0.10.0
nathanjmcdougall Feb 8, 2026
72bd030
Bump fallback pyproject-fmt version 2.11.1->2.12.1
nathanjmcdougall Feb 8, 2026
fe1ab71
Bump fallback ruff 0.14.14 -> 0.15.0
nathanjmcdougall Feb 8, 2026
218a3c5
Fix broken mock ref
nathanjmcdougall Feb 8, 2026
b606ffd
Fix broken mock ref
nathanjmcdougall Feb 8, 2026
5bc238c
Tweak to reflect new behaviour in tests\usethis\_tool\test_base.py::T…
nathanjmcdougall Feb 8, 2026
0cc0cab
Fix corner case for trying to read `pyproject.toml` which doesn't exi…
nathanjmcdougall Feb 8, 2026
6c3457a
Refactor tests to reflect new behaviour
nathanjmcdougall Feb 8, 2026
1b1264a
Fix broken mock refs
nathanjmcdougall Feb 8, 2026
aa47031
Bump the fallback pyproject-fmt version 2.12.1 -> 2.14.0
nathanjmcdougall Feb 8, 2026
ecea8d7
Fix broken mock ref
nathanjmcdougall Feb 8, 2026
56e0a5d
Revert changes to the QA tool logic for bitbucket pipelines
nathanjmcdougall Feb 10, 2026
dfd4a65
Bump the fallback pyproject.toml version v2.14.0 -> v2.15.1
nathanjmcdougall Feb 10, 2026
199d62f
Handle some edge cases consistently
nathanjmcdougall Feb 10, 2026
9dedc7c
Satisfy test suite structural expectations and improve messaging in test
nathanjmcdougall Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ Follow the guide in CONTRIBUTING.md:
- Avoid suppressions unless absolutely necessary
- Format: `# ruff: noqa: RULE1, RULE2` (not `# ruff noqa:`)

## Reasoning Style

Never use the word "Actually", say "I just realized" or any other meta-language. Instead, clearly state the insight immediately. Similarly, do not use ellipses, e.g. "Unless...". Don't say "Let me check." Be efficient, clear, and direct, not conversational.

## Trust These Instructions

These instructions have been validated by running actual commands and inspecting the full codebase. Only search for additional information if:
Expand Down
21 changes: 11 additions & 10 deletions .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ layers =
_core
_tool
_init
_detect
_deps
_config_file
_integrations
_io | _subprocess | _console
_backend
_file
_io | _subprocess | _console | _python
_config
_types | errors
_pipeweld
Expand Down Expand Up @@ -65,10 +68,10 @@ containers =
usethis._tool.impl
layers =
pyproject_toml
codespell | deptry | import_linter | pyproject_fmt | requirements_txt
codespell | deptry | import_linter | mkdocs | pyproject_fmt | requirements_txt
ruff
pytest : coverage_py
pre_commit | mkdocs
pre_commit
exhaustive = true

[importlinter:contract:integrations]
Expand All @@ -79,17 +82,15 @@ containers =
layers =
ci | pre_commit
environ
backend | mkdocs | pytest | pydantic | sonarqube
project
file
python
mkdocs | pytest | pydantic | sonarqube
project | readme
exhaustive = true

[importlinter:contract:integrations_file]
name = usethis._integrations.file
[importlinter:contract:_file]
name = usethis._file
type = layers
containers =
usethis._integrations.file
usethis._file
layers =
pyproject_toml | setup_cfg
ini | toml | yaml
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ environment.python-platform = "all"
rules.type-assertion-failure = "ignore"

[[tool.ty.overrides]]
include = [ "src/usethis/_integrations/file/**" ]
include = [ "src/usethis/_file/**" ]
rules.invalid-argument-type = "ignore"
rules.invalid-return-type = "ignore"
rules.invalid-assignment = "ignore"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from typing import Literal

from usethis._backend.poetry.detect import is_poetry_used
from usethis._backend.uv.available import is_uv_available
from usethis._backend.uv.detect import is_uv_used
from usethis._config import usethis_config
from usethis._console import warn_print
from usethis._integrations.backend.poetry.used import is_poetry_used
from usethis._integrations.backend.uv.available import is_uv_available
from usethis._integrations.backend.uv.used import is_uv_used
from usethis._types.backend import BackendEnum


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from usethis._config import usethis_config
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager


def is_poetry_used() -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from usethis._integrations.backend.uv.call import call_uv_subprocess
from usethis._integrations.backend.uv.errors import UVSubprocessFailedError
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.errors import UVSubprocessFailedError


def is_uv_available() -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from __future__ import annotations

from usethis._backend.uv.errors import UVSubprocessFailedError
from usethis._backend.uv.link_mode import ensure_symlink_mode
from usethis._backend.uv.toml import UVTOMLManager
from usethis._config import usethis_config
from usethis._integrations.backend.uv.errors import UVSubprocessFailedError
from usethis._integrations.backend.uv.link_mode import ensure_symlink_mode
from usethis._integrations.backend.uv.toml import UVTOMLManager
from usethis._integrations.file.pyproject_toml.io_ import (
from usethis._file.pyproject_toml.io_ import (
PyprojectTOMLManager,
)
from usethis._integrations.file.pyproject_toml.valid import ensure_pyproject_validity
from usethis._file.pyproject_toml.valid import ensure_pyproject_validity
from usethis._subprocess import SubprocessFailedError, call_subprocess
from usethis._types.backend import BackendEnum
from usethis.errors import ForbiddenBackendError
Expand All @@ -27,8 +27,6 @@ def call_uv_subprocess(args: list[str], change_toml: bool) -> str:
msg = f"The '{usethis_config.backend.value}' backend is enabled, but a uv subprocess was invoked."
raise ForbiddenBackendError(msg)

is_pyproject_toml = (usethis_config.cpd() / "pyproject.toml").exists()

if change_toml and args[0] in {
"lock",
"add",
Expand All @@ -40,14 +38,8 @@ def call_uv_subprocess(args: list[str], change_toml: bool) -> str:
}:
ensure_symlink_mode()

if is_pyproject_toml and change_toml:
if PyprojectTOMLManager().is_locked():
ensure_pyproject_validity()
PyprojectTOMLManager().write_file()
PyprojectTOMLManager()._content = None
else:
with PyprojectTOMLManager():
ensure_pyproject_validity()
if change_toml:
_prepare_pyproject_write()

if usethis_config.frozen and args[0] in {
# Note, not "lock", for which the --frozen flags has quite a different effect
Expand Down Expand Up @@ -83,6 +75,22 @@ def call_uv_subprocess(args: list[str], change_toml: bool) -> str:
return output


def _prepare_pyproject_write() -> None:
is_pyproject_toml = (usethis_config.cpd() / "pyproject.toml").exists()
is_locked = PyprojectTOMLManager().is_locked()

if is_pyproject_toml and is_locked:
ensure_pyproject_validity()
PyprojectTOMLManager().write_file()
PyprojectTOMLManager()._content = None # Basically a cache clear
elif not is_pyproject_toml and is_locked:
# Similarly; cache clear
PyprojectTOMLManager()._content = None
elif is_pyproject_toml:
with PyprojectTOMLManager():
ensure_pyproject_validity()


def add_default_groups_via_uv(groups: list[str]) -> None:
"""Add default groups using the uv command-line tool."""
if UVTOMLManager().path.exists():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from typing import TYPE_CHECKING

from usethis._integrations.backend.uv.call import call_uv_subprocess
from usethis._integrations.backend.uv.errors import (
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.errors import (
UVDepGroupError,
UVSubprocessFailedError,
)
from usethis._integrations.backend.uv.toml import UVTOMLManager
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._backend.uv.toml import UVTOMLManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager

if TYPE_CHECKING:
from usethis._types.deps import Dependency
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from usethis._config import usethis_config
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager


def is_uv_used() -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

from usethis._config import usethis_config
from usethis._integrations.backend.uv import ( # Use this style to allow test mocking
from usethis._backend.uv import ( # Use this style to allow test mocking
call,
)
from usethis._integrations.backend.uv.errors import UVInitError, UVSubprocessFailedError
from usethis._integrations.file.pyproject_toml.errors import PyprojectTOMLInitError
from usethis._backend.uv.errors import UVInitError, UVSubprocessFailedError
from usethis._config import usethis_config
from usethis._file.pyproject_toml.errors import PyprojectTOMLInitError


def opinionated_uv_init() -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import contextlib

from usethis._integrations.backend.uv.toml import UVTOMLManager
from usethis._integrations.file.pyproject_toml.errors import (
from usethis._backend.uv.toml import UVTOMLManager
from usethis._file.pyproject_toml.errors import (
PyprojectTOMLValueAlreadySetError,
)
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager


def ensure_symlink_mode() -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from usethis._backend.uv.call import call_uv_subprocess
from usethis._config import usethis_config
from usethis._console import tick_print
from usethis._integrations.backend.uv.call import call_uv_subprocess


def ensure_uv_lock() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import re

from usethis._integrations.backend.uv.call import call_uv_subprocess
from usethis._integrations.backend.uv.errors import UVUnparsedPythonVersionError
from usethis._integrations.file.pyproject_toml.errors import PyprojectTOMLNotFoundError
from usethis._integrations.file.pyproject_toml.requires_python import (
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.errors import UVUnparsedPythonVersionError
from usethis._file.pyproject_toml.errors import PyprojectTOMLNotFoundError
from usethis._file.pyproject_toml.requires_python import (
MissingRequiresPythonError,
get_requires_python,
)
from usethis._integrations.python.version import PythonVersion
from usethis._python.version import PythonVersion


def get_available_uv_python_versions() -> set[str]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from usethis._integrations.file.toml.io_ import TOMLFileManager
from usethis._file.toml.io_ import TOMLFileManager


class UVTOMLManager(TOMLFileManager):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json

from usethis._integrations.backend.uv.call import call_uv_subprocess
from usethis._integrations.backend.uv.errors import UVSubprocessFailedError
from usethis._backend.uv.call import call_uv_subprocess
from usethis._backend.uv.errors import UVSubprocessFailedError

FALLBACK_UV_VERSION = "0.9.21"
FALLBACK_UV_VERSION = "0.10.0"


def get_uv_version() -> str:
Expand Down
12 changes: 6 additions & 6 deletions src/usethis/_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from pathlib import Path
from typing import TYPE_CHECKING

from usethis._integrations.backend.uv.toml import UVTOMLManager
from usethis._backend.uv.toml import UVTOMLManager
from usethis._file.ini.io_ import INIFileManager
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._file.setup_cfg.io_ import SetupCFGManager
from usethis._file.toml.io_ import TOMLFileManager
from usethis._file.yaml.io_ import YAMLFileManager
from usethis._integrations.ci.bitbucket.yaml import BitbucketPipelinesYAMLManager
from usethis._integrations.file.ini.io_ import INIFileManager
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.file.setup_cfg.io_ import SetupCFGManager
from usethis._integrations.file.toml.io_ import TOMLFileManager
from usethis._integrations.file.yaml.io_ import YAMLFileManager
from usethis._integrations.pre_commit.yaml import PreCommitConfigYAMLManager

if TYPE_CHECKING:
Expand Down
2 changes: 1 addition & 1 deletion src/usethis/_core/author.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from usethis._console import tick_print
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._init import ensure_pyproject_toml
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager


def add_author(
Expand Down
6 changes: 3 additions & 3 deletions src/usethis/_core/badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

from usethis._config import usethis_config
from usethis._console import plain_print, tick_print, warn_print
from usethis._core.readme import (
from usethis._core.readme import add_readme
from usethis._integrations.project.name import get_project_name
from usethis._integrations.readme.path import (
NonMarkdownREADMEError,
add_readme,
get_markdown_readme_path,
)
from usethis._integrations.project.name import get_project_name

if TYPE_CHECKING:
from typing_extensions import Self
Expand Down
35 changes: 13 additions & 22 deletions src/usethis/_core/ci.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from usethis._console import how_print, info_print
from usethis._integrations.ci.bitbucket.config import (
add_bitbucket_pipelines_config,
remove_bitbucket_pipelines_config,
)
from usethis._tool.all_ import ALL_TOOLS
from usethis._tool.impl.codespell import CodespellTool
from usethis._tool.impl.deptry import DeptryTool
from usethis._tool.impl.import_linter import ImportLinterTool
Expand All @@ -15,13 +14,10 @@
from usethis._tool.impl.pytest import PytestTool
from usethis._tool.impl.ruff import RuffTool

if TYPE_CHECKING:
from usethis._tool.base import Tool

# These are tools would run via pre-commit if available
_CI_QA_TOOLS: list[type[Tool]] = [ # Not including pytest and pre-commit
# This order should match the canonical order in the function which adds
# steps
# Ordered list of QA tools that should run in CI (matches canonical step order)
# These tools run via pre-commit if available, otherwise directly in CI
_CI_QA_TOOL_TYPES = [
PreCommitTool,
PyprojectFmtTool,
RuffTool,
DeptryTool,
Expand All @@ -38,18 +34,17 @@ def use_ci_bitbucket(
return

if not remove:
use_pre_commit = PreCommitTool().is_used()
use_any_tool = (
use_pre_commit or PytestTool().is_used() or _using_any_ci_qa_tools()
)
use_any_tool = any(tool.is_used() for tool in ALL_TOOLS)

add_bitbucket_pipelines_config(report_placeholder=not use_any_tool)

if use_pre_commit:
PreCommitTool().update_bitbucket_steps()
else:
for tool in _CI_QA_TOOLS:
tool().update_bitbucket_steps()
# Add steps for QA tools in canonical order
for tool_type in _CI_QA_TOOL_TYPES:
# Find the matching tool instance in ALL_TOOLS
for tool in ALL_TOOLS:
if isinstance(tool, tool_type):
tool.update_bitbucket_steps()
break

PytestTool().update_bitbucket_steps(matrix_python=matrix_python)

Expand All @@ -58,10 +53,6 @@ def use_ci_bitbucket(
remove_bitbucket_pipelines_config()


def _using_any_ci_qa_tools():
return any(tool().is_used() for tool in _CI_QA_TOOLS)


def print_how_to_use_ci_bitbucket() -> None:
"""Print how to use the Bitbucket CI service."""
_suggest_pytest()
Expand Down
4 changes: 2 additions & 2 deletions src/usethis/_core/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from typing_extensions import assert_never

from usethis._console import table_print
from usethis._core.readme import is_readme_used
from usethis._integrations.ci.bitbucket.used import is_bitbucket_used
from usethis._detect.ci.bitbucket import is_bitbucket_used
from usethis._detect.readme import is_readme_used
from usethis._tool.all_ import ALL_TOOLS
from usethis._tool.impl.ruff import RuffTool

Expand Down
Loading
Loading