Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions src/usethis/_core/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from usethis._backend.uv.lockfile import ensure_uv_lock
from usethis._config import usethis_config
from usethis._console import info_print, instruct_print, tick_print
from usethis._deps import add_deps_to_group, remove_deps_from_group
from usethis._file.pyproject_toml.valid import ensure_pyproject_validity
from usethis._init import ensure_dep_declaration_file, write_simple_requirements_txt
from usethis._integrations.mkdocs.core import add_docs_dir
Expand All @@ -37,6 +38,7 @@
from usethis._tool.impl.base.ty import TyTool
from usethis._tool.rule import RuleConfig
from usethis._types.backend import BackendEnum
from usethis._types.deps import Dependency

if TYPE_CHECKING:
from usethis._tool.all_ import SupportedToolType
Expand Down Expand Up @@ -317,7 +319,13 @@ def use_requirements_txt(*, remove: bool = False, how: bool = False) -> None:
backend = get_backend()

if PreCommitTool().is_used():
# pyproject.toml is needed for config items and dependency groups
ensure_dep_declaration_file()
tool.add_pre_commit_config()
tool.add_configs()
if backend is BackendEnum.uv:
with usethis_config.set(quiet=True):
add_deps_to_group([Dependency(name="uv")], "uv", default=False)

if path.exists():
# requirements file already exists - short circuit; only need to explain how
Expand Down Expand Up @@ -356,6 +364,9 @@ def use_requirements_txt(*, remove: bool = False, how: bool = False) -> None:
tool.print_how_to_use()
else:
tool.remove_pre_commit_repo_configs()
tool.remove_configs()
with usethis_config.set(quiet=True):
remove_deps_from_group([Dependency(name="uv")], "uv")
tool.remove_managed_files()


Expand Down
22 changes: 20 additions & 2 deletions src/usethis/_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from typing import Literal

import pydantic
from packaging.requirements import Requirement
from pydantic import TypeAdapter
Expand Down Expand Up @@ -201,8 +203,17 @@ def is_dep_in_any_group(dep: Dependency) -> bool:
)


def add_deps_to_group(deps: list[Dependency], group: str) -> None:
"""Add a package as a non-build dependency using PEP 735 dependency groups."""
def add_deps_to_group(
deps: list[Dependency], group: str, *, default: bool = True
) -> None:
"""Add a package as a non-build dependency using PEP 735 dependency groups.

Args:
deps: The dependencies to add to the group.
group: The name of the dependency group.
default: Whether to register the group as a default group. Set to False
for groups that should be declared but not installed by default.
"""
existing_group = get_deps_from_group(group)

to_add_deps = [
Expand Down Expand Up @@ -240,6 +251,13 @@ def add_deps_to_group(deps: list[Dependency], group: str) -> None:
assert_never(backend)

# Register the group - don't do this before adding the deps in case that step fails
if default:
_register_default_group(group, backend=backend)


def _register_default_group(
group: str, *, backend: Literal[BackendEnum.uv, BackendEnum.none]
) -> None:
if backend is BackendEnum.uv:
register_default_group(group)
elif backend is BackendEnum.none:
Expand Down
51 changes: 41 additions & 10 deletions src/usethis/_tool/impl/spec/requirements_txt.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
from typing_extensions import assert_never, override

from usethis._backend.dispatch import get_backend
from usethis._fallback import FALLBACK_UV_VERSION
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.pre_commit import schema as pre_commit_schema
from usethis._integrations.pre_commit.language import get_system_language
from usethis._tool.base import ToolMeta, ToolSpec
from usethis._tool.config import ConfigEntry, ConfigItem, ConfigSpec
from usethis._tool.pre_commit import PreCommitConfig
from usethis._types.backend import BackendEnum

_UV_PRE_COMMIT_REPO = "https://github.com/astral-sh/uv-pre-commit"


class RequirementsTxtToolSpec(ToolSpec):
@final
Expand All @@ -33,24 +37,51 @@ def pre_commit_config(self) -> PreCommitConfig:

if backend is BackendEnum.uv:
return PreCommitConfig.from_single_repo(
pre_commit_schema.LocalRepo(
repo="local",
pre_commit_schema.UriRepo(
repo=_UV_PRE_COMMIT_REPO,
rev=FALLBACK_UV_VERSION,
hooks=[
pre_commit_schema.HookDefinition(
id="uv-export",
name="uv-export",
files="^uv\\.lock$",
pass_filenames=False,
entry="uv export --frozen --offline --quiet -o=requirements.txt",
language=get_system_language(),
require_serial=True,
)
],
),
requires_venv=True,
requires_venv=False,
)
elif backend is BackendEnum.none:
# Need a backend to generate requirements.txt files
return PreCommitConfig(repo_configs=[], inform_how_to_use_on_migrate=False)
else:
assert_never(backend)

@override
@final
def config_spec(self) -> ConfigSpec:
backend = get_backend()

if backend is BackendEnum.uv:
return ConfigSpec.from_flat(
file_managers=[PyprojectTOMLManager()],
resolution="first",
config_items=[
ConfigItem(
description="Sync with UV repo-to-package mapping for uv-pre-commit",
root={
Path("pyproject.toml"): ConfigEntry(
keys=[
"tool",
"sync-with-uv",
"repo-to-package",
_UV_PRE_COMMIT_REPO,
],
get_value=lambda: "uv",
)
},
managed=False,
),
],
)
elif backend is BackendEnum.none:
return ConfigSpec.empty()
else:
assert_never(backend)
16 changes: 8 additions & 8 deletions tests/usethis/_core/test_core_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
use_ty,
)
from usethis._deps import add_deps_to_group, get_deps_from_group, is_dep_satisfied_in
from usethis._fallback import FALLBACK_RUFF_VERSION, FALLBACK_SYNC_WITH_UV_VERSION
from usethis._fallback import (
FALLBACK_RUFF_VERSION,
FALLBACK_SYNC_WITH_UV_VERSION,
FALLBACK_UV_VERSION,
)
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.pre_commit.hooks import _HOOK_ORDER, get_hook_ids
from usethis._integrations.pre_commit.yaml import PreCommitConfigYAMLManager
Expand Down Expand Up @@ -2966,21 +2970,17 @@ def test_pre_commit(
rev: {FALLBACK_SYNC_WITH_UV_VERSION}
hooks:
- id: sync-with-uv
- repo: local
- repo: https://github.com/astral-sh/uv-pre-commit
rev: {FALLBACK_UV_VERSION}
hooks:
- id: uv-export
name: uv-export
files: ^uv\\.lock$
entry: uv export --frozen --offline --quiet -o=requirements.txt
language: system
pass_filenames: false
require_serial: true
"""
)
out, err = capfd.readouterr()
assert not err
assert out == (
"✔ Adding hook 'uv-export' to '.pre-commit-config.yaml'.\n"
"✔ Adding requirements.txt config to 'pyproject.toml'.\n"
"✔ Writing 'requirements.txt'.\n"
"☐ Run 'uv run pre-commit run -a uv-export' to write 'requirements.txt'.\n"
)
Expand Down
Loading