Skip to content

Add --backend flag to over-ride manual detection of uv#826

Merged
nathanjmcdougall merged 69 commits intomainfrom
241-flag-to-avoid-calling-uv
Aug 3, 2025
Merged

Add --backend flag to over-ride manual detection of uv#826
nathanjmcdougall merged 69 commits intomainfrom
241-flag-to-avoid-calling-uv

Conversation

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator

This involves some major architectural changes.

@nathanjmcdougall nathanjmcdougall linked an issue Jun 29, 2025 that may be closed by this pull request
@codecov
Copy link
Copy Markdown

codecov bot commented Jun 30, 2025

❌ 44 Tests Failed:

Tests completed Failed Passed Skipped
1298 44 1254 6
View the top 3 failed test(s) by shortest run time
tests/usethis/_tool/impl/test_coverage_py.py::TestCoveragePyTool::TestAddConfigs::test_after_codespell
Stack Traces | 0.022s run time
self = <tests.usethis._tool.impl.test_coverage_py.TestCoveragePyTool.TestAddConfigs object at 0x7f50f7e13750>
tmp_path = PosixPath('.../pytest-of-runner/pytest-0/test_after_codespell0')

            def test_after_codespell(self, tmp_path: Path):
                # To check the config is valid
                # https://github..../usethis-python/issues/558
    
                # Arrange
                (tmp_path / "pyproject.toml").write_text("""\
    [project]
    name = "example"
    version = "0.1.0"
    description = "Add your description here"
    
    [dependency-groups]
    dev = [
        "codespell>=2.4.1",
    ]
    
    [tool.codespell]
    ignore-regex = ["[A-Za-z0-9+/]{100,}"]
    """)
    
                # Act
                with change_cwd(tmp_path), files_manager():
                    CoveragePyTool().add_configs()
    
                # Assert
                with change_cwd(tmp_path), files_manager():
                    assert ["tool", "coverage"] in PyprojectTOMLManager()
>               assert "[tool.coverage]" in (tmp_path / "pyproject.toml").read_text()
E               assert '[tool.coverage]' in '[project]\nname = "example"\nversion = "0.1.0"\ndescription = "Add your description here"\n\n[dependency-groups]\ndev = [\n    "codespell>=2.4.1",\n]\n                                                    \n[tool.codespell]\nignore-regex = ["[A-Za-z0-9+/]{100,}"]\n\n[tool.coverage.run]\nsource = ["."]\n'
E                +  where '[project]\nname = "example"\nversion = "0.1.0"\ndescription = "Add your description here"\n\n[dependency-groups]\ndev = [\n    "codespell>=2.4.1",\n]\n                                                    \n[tool.codespell]\nignore-regex = ["[A-Za-z0-9+/]{100,}"]\n\n[tool.coverage.run]\nsource = ["."]\n' = read_text()
E                +    where read_text = (PosixPath('.../pytest-of-runner/pytest-0/test_after_codespell0') / 'pyproject.toml').read_text

self       = <tests.usethis._tool.impl.test_coverage_py.TestCoveragePyTool.TestAddConfigs object at 0x7f50f7e13750>
tmp_path   = PosixPath('.../pytest-of-runner/pytest-0/test_after_codespell0')

.../_tool/impl/test_coverage_py.py:38: AssertionError
tests/usethis/_core/test_core_tool.py::TestCodespell::TestAdd::test_codespell_rc_file[NetworkConn.OFFLINE]
Stack Traces | 0.046s run time
self = <tests.usethis._core.test_core_tool.TestCodespell.TestAdd object at 0x7f50f7aa4140>
uv_init_dir = PosixPath('.../pytest-of-runner/pytest-0/test_codespell_rc_file_Network1')

            @pytest.mark.usefixtures("_vary_network_conn")
            def test_codespell_rc_file(self, uv_init_dir: Path):
                # This file is only preferred to pyproject.toml if there's already
                # some codespell config in the file
    
                # Arrange
                (uv_init_dir / ".codespellrc").write_text(
                    """\
    [codespell]
    fake = bar
    """
                )
    
                # Act
                with change_cwd(uv_init_dir), files_manager():
                    use_codespell()
    
                # Assert
>               assert (uv_init_dir / ".codespellrc").read_text() == (
                    """\
    [codespell]
    fake = bar
    ignore-regex = [A-Za-z0-9+/]{100,}
    ignore-words-list = ...
    """
                )
E               AssertionError: assert '[codespell]\...-9+/]{100,}\n' == '[codespell]\...-list = ...\n'
E                 
E                   [codespell]
E                   fake = bar
E                   ignore-regex = [A-Za-z0-9+/]{100,}
E                 - ignore-words-list = ...

self       = <tests.usethis._core.test_core_tool.TestCodespell.TestAdd object at 0x7f50f7aa4140>
uv_init_dir = PosixPath('.../pytest-of-runner/pytest-0/test_codespell_rc_file_Network1')

.../usethis/_core/test_core_tool.py:172: AssertionError
tests/usethis/_core/test_core_tool.py::TestCodespell::TestAdd::test_codespell_rc_file[NetworkConn.ONLINE]
Stack Traces | 0.047s run time
self = <tests.usethis._core.test_core_tool.TestCodespell.TestAdd object at 0x7f50f7a94250>
uv_init_dir = PosixPath('.../pytest-of-runner/pytest-0/test_codespell_rc_file_Network0')

            @pytest.mark.usefixtures("_vary_network_conn")
            def test_codespell_rc_file(self, uv_init_dir: Path):
                # This file is only preferred to pyproject.toml if there's already
                # some codespell config in the file
    
                # Arrange
                (uv_init_dir / ".codespellrc").write_text(
                    """\
    [codespell]
    fake = bar
    """
                )
    
                # Act
                with change_cwd(uv_init_dir), files_manager():
                    use_codespell()
    
                # Assert
>               assert (uv_init_dir / ".codespellrc").read_text() == (
                    """\
    [codespell]
    fake = bar
    ignore-regex = [A-Za-z0-9+/]{100,}
    ignore-words-list = ...
    """
                )
E               AssertionError: assert '[codespell]\...-9+/]{100,}\n' == '[codespell]\...-list = ...\n'
E                 
E                   [codespell]
E                   fake = bar
E                   ignore-regex = [A-Za-z0-9+/]{100,}
E                 - ignore-words-list = ...

self       = <tests.usethis._core.test_core_tool.TestCodespell.TestAdd object at 0x7f50f7a94250>
uv_init_dir = PosixPath('.../pytest-of-runner/pytest-0/test_codespell_rc_file_Network0')

.../usethis/_core/test_core_tool.py:172: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Jun 30, 2025

CodSpeed Instrumentation Performance Report

Merging #826 will degrade performances by 8.46%

Comparing 241-flag-to-avoid-calling-uv (e3aa6e5) with main (5d0b818)

Summary

❌ 1 (👁 1) regressions
🆕 1 new benchmarks
⁉️ 1 (👁 1) dropped benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
👁 test_help_flag 39.2 ms 42.9 ms -8.46%
👁 test_several_tools_add_and_remove 1.4 s N/A N/A
🆕 test_several_tools_add_and_remove N/A 1.5 s N/A

@nathanjmcdougall nathanjmcdougall marked this pull request as ready for review July 12, 2025 04:06
@nathanjmcdougall nathanjmcdougall requested a review from Copilot July 12, 2025 04:06

This comment was marked as outdated.

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

There are still a couple of major things to do here:

  • There's still lots of calls to call_uv_subprocess which aren't protected by backend dispatching
  • Testing the higher-level interfaces with a none backend

@nathanjmcdougall nathanjmcdougall changed the title Add --backend flag to over-ride manual detection of uv Add --backend flag to over-ride manual detection of uv Jul 12, 2025
@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

nathanjmcdougall commented Jul 12, 2025

Another thing to do - we shouldn't create pyproject.toml anymore unless it's truly necessary. With the none backend, we aren't adding deps in the pyproject.toml file (or anywhere), just displaying a message, so it shouldn't be necessary to create this file.

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

And yet another thing - make sure all the --how / print_how_to_use messages are respecting the backend choice

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

Now testing all interfaces which support the --backend option.

Need to consider whether the usethis ci commands should respect backend choice internally to the CI config. I think they need to, in which case that needs to be implemented and tested.

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

nathanjmcdougall commented Jul 23, 2025

Blocked by #663, since we are assuming empty files are valid here:

for file_manager in file_managers:
if not (file_manager.path.exists() and file_manager.path.is_file()):
# If the file doesn't exist, we will create it
file_manager.path.touch(exist_ok=True)

We should test that empty files are valid for all key-value file managers, probably via a constant that stores all of them (#905)

Also, I'd like to break up the complexity here so we don't need to disable the Ruff rule:

def add_configs(self) -> None: # noqa: PLR0912
"""Add the tool's configuration sections.

@nathanjmcdougall nathanjmcdougall linked an issue Jul 25, 2025 that may be closed by this pull request
@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

nathanjmcdougall commented Jul 25, 2025

I think making the CI config backend agnostic is an important next step, but it really belongs in a separate PR. At the moment, the CI completely assumes uv backend. And the same goes for pre-commit config per #273

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

Hitting #912 and some tests are out of date based on the new #908 behaviour

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

#912 should just be resolved here in this branch because I think managed-file creation is a new functionality/default behaviour (should check this).

for file_manager in used_file_managers:
if not (file_manager.path.exists() and file_manager.path.is_file()):
# If the file doesn't exist, we will create it
file_manager.path.touch(exist_ok=True)

@nathanjmcdougall
Copy link
Copy Markdown
Collaborator Author

I think everything is ready except to make sure all the --how / print_how_to_use messages are respecting the backend choice, and in general checking invocations of is_uv_used to determine whether they're better replaced by a backend-based logic

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a --backend flag to override manual detection of UV, representing a major architectural change to support multiple package manager backends. The changes introduce a backend enum system that allows usethis to work with or without UV, providing better flexibility for users who may not have UV installed.

Key changes:

  • Introduction of a backend system with BackendEnum (auto, uv, none) to control package manager behavior
  • Major reorganization of the codebase structure, moving interface modules and creating backend dispatch logic
  • Addition of --backend flag across all CLI commands to allow manual backend selection

Reviewed Changes

Copilot reviewed 109 out of 121 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/usethis/_types/backend.py Defines the new BackendEnum for backend selection
src/usethis/_integrations/backend/dispatch.py Implements backend detection and selection logic
src/usethis/_ui/options.py Adds the backend option for CLI commands
src/usethis/_config.py Updates configuration to include backend settings
src/usethis/_deps.py Refactors dependency management to work with multiple backends
src/usethis/_init.py Creates unified project initialization logic
Multiple test files Updates tests to work with the new backend system
Comments suppressed due to low confidence (2)

src/usethis/_types/deps.py:14

  • The method name to_requirement_string is inconsistent with the method call to_requirements_string used in the old code. This creates an inconsistency that could cause issues.
    def to_requirement_string(self) -> str:

tests/usethis/_types/test_deps.py:8

  • The test uses to_requirement_string() which matches the new implementation, but this inconsistency with the old to_requirements_string() method name should be addressed for consistency.
            assert dep.to_requirement_string() == "requests"

@nathanjmcdougall nathanjmcdougall merged commit f44669a into main Aug 3, 2025
19 checks passed
@nathanjmcdougall nathanjmcdougall deleted the 241-flag-to-avoid-calling-uv branch August 3, 2025 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants