uv-upsync 1.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uv_upsync/__init__.py +6 -0
- uv_upsync/__main__.py +150 -0
- uv_upsync/commands.py +43 -0
- uv_upsync/exceptions.py +38 -0
- uv_upsync/logging.py +34 -0
- uv_upsync/parsers.py +142 -0
- uv_upsync/pypi.py +33 -0
- uv_upsync/uv.py +24 -0
- uv_upsync-1.1.2.dist-info/METADATA +238 -0
- uv_upsync-1.1.2.dist-info/RECORD +13 -0
- uv_upsync-1.1.2.dist-info/WHEEL +4 -0
- uv_upsync-1.1.2.dist-info/entry_points.txt +2 -0
- uv_upsync-1.1.2.dist-info/licenses/LICENSE +21 -0
uv_upsync/__init__.py
ADDED
uv_upsync/__main__.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Module that contains the main entry point for the uv-upsync."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import tomlkit
|
|
10
|
+
|
|
11
|
+
from uv_upsync import commands
|
|
12
|
+
from uv_upsync import logging
|
|
13
|
+
from uv_upsync import parsers
|
|
14
|
+
from uv_upsync import uv
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.Logger()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command(cls=commands.Command)
|
|
21
|
+
@click.option(
|
|
22
|
+
"-f",
|
|
23
|
+
"--filepath",
|
|
24
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
25
|
+
default=pathlib.Path.cwd() / "pyproject.toml",
|
|
26
|
+
help=f"Filepath to the pyproject.toml file "
|
|
27
|
+
f"(default: {click.style('./pyproject.toml', fg='magenta')})",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--exclude",
|
|
31
|
+
type=click.STRING,
|
|
32
|
+
multiple=True,
|
|
33
|
+
default=(),
|
|
34
|
+
help="Packages to exclude from updating "
|
|
35
|
+
f"({click.style('multiple values allowed', fg='magenta')})",
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"--group",
|
|
39
|
+
type=click.STRING,
|
|
40
|
+
multiple=True,
|
|
41
|
+
default=(),
|
|
42
|
+
help="Specific dependency group(s) to update. Can be 'project', optional-dependencies names, "
|
|
43
|
+
f"or dependency-groups names ({click.style('multiple values allowed', fg='magenta')}). "
|
|
44
|
+
"If not specified, all groups are updated.",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--dry-run",
|
|
48
|
+
is_flag=True,
|
|
49
|
+
type=click.BOOL,
|
|
50
|
+
default=False,
|
|
51
|
+
help="Preview changes without writing to pyproject.toml",
|
|
52
|
+
)
|
|
53
|
+
def main( # noqa: C901, PLR0912, PLR0915
|
|
54
|
+
filepath: pathlib.Path,
|
|
55
|
+
exclude: tuple[str, ...],
|
|
56
|
+
group: tuple[str, ...],
|
|
57
|
+
*,
|
|
58
|
+
dry_run: bool,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""uv-upsync - is a tool for automated dependency updates and version bumping in pyproject.toml.""" # noqa: E501
|
|
61
|
+
with filepath.open() as toml_file:
|
|
62
|
+
pyproject = tomlkit.load(toml_file)
|
|
63
|
+
bk_pyproject = copy.deepcopy(pyproject)
|
|
64
|
+
|
|
65
|
+
dependencies_groups = parsers.get_dependencies_groups(pyproject)
|
|
66
|
+
|
|
67
|
+
for group_name, dependency_group in dependencies_groups.items():
|
|
68
|
+
match group_name:
|
|
69
|
+
case "project":
|
|
70
|
+
match group:
|
|
71
|
+
case () if not group:
|
|
72
|
+
pass
|
|
73
|
+
case _ if "project" in group:
|
|
74
|
+
pass
|
|
75
|
+
case _:
|
|
76
|
+
logger.warning("Skipping 'project' dependencies")
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
updated_dependency_specifiers = parsers.update_dependency_specifiers(
|
|
80
|
+
dependency_group,
|
|
81
|
+
exclude,
|
|
82
|
+
)
|
|
83
|
+
dependency_specifiers = pyproject["project"]["dependencies"] # type: ignore[index]
|
|
84
|
+
for index in range(len(dependency_specifiers)): # type: ignore[arg-type]
|
|
85
|
+
dependency_specifiers[index] = updated_dependency_specifiers[index] # type: ignore[index]
|
|
86
|
+
|
|
87
|
+
case "optional-dependencies":
|
|
88
|
+
dependency_groups = pyproject["project"][group_name] # type: ignore[index]
|
|
89
|
+
for subgroup_name, dependency_specifiers in dependency_groups.items(): # type: ignore[union-attr]
|
|
90
|
+
match group:
|
|
91
|
+
case () if not group:
|
|
92
|
+
pass
|
|
93
|
+
case _ if subgroup_name in group:
|
|
94
|
+
pass
|
|
95
|
+
case _:
|
|
96
|
+
logger.warning(f"Skipping 'optional-dependencies.{subgroup_name}'")
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
updated_dependency_specifiers = parsers.update_dependency_specifiers(
|
|
100
|
+
dependency_specifiers,
|
|
101
|
+
exclude,
|
|
102
|
+
)
|
|
103
|
+
for index in range(len(dependency_specifiers)):
|
|
104
|
+
dependency_specifiers[index] = updated_dependency_specifiers[index]
|
|
105
|
+
|
|
106
|
+
case "dependency-groups":
|
|
107
|
+
dependency_groups = pyproject[group_name]
|
|
108
|
+
for subgroup_name, dependency_specifiers in dependency_groups.items(): # type: ignore[union-attr]
|
|
109
|
+
match group:
|
|
110
|
+
case () if not group:
|
|
111
|
+
pass
|
|
112
|
+
case _ if subgroup_name in group:
|
|
113
|
+
pass
|
|
114
|
+
case _:
|
|
115
|
+
logger.warning(f"Skipping 'dependency-groups.{subgroup_name}'")
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
updated_dependency_specifiers = parsers.update_dependency_specifiers(
|
|
119
|
+
dependency_specifiers,
|
|
120
|
+
exclude,
|
|
121
|
+
)
|
|
122
|
+
for index in range(len(dependency_specifiers)):
|
|
123
|
+
dependency_specifiers[index] = updated_dependency_specifiers[index]
|
|
124
|
+
case _:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
match dry_run:
|
|
128
|
+
case True:
|
|
129
|
+
logger.warning(
|
|
130
|
+
"Dry run mode enabled, no changes will be made to the pyproject.toml file",
|
|
131
|
+
)
|
|
132
|
+
case False:
|
|
133
|
+
with filepath.open("w") as toml_file:
|
|
134
|
+
tomlkit.dump(pyproject, toml_file)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
logger.info("Resolving dependencies...")
|
|
138
|
+
uv.lock()
|
|
139
|
+
logger.info("Dependencies resolved")
|
|
140
|
+
except Exception as exception:
|
|
141
|
+
logger.exception(
|
|
142
|
+
"Failed to lock the dependencies. Rolling back changes",
|
|
143
|
+
exception,
|
|
144
|
+
)
|
|
145
|
+
with filepath.open("w") as toml_file:
|
|
146
|
+
tomlkit.dump(bk_pyproject, toml_file)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
uv_upsync/commands.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Module that contains custom implementation of the `click.Command` with enriched formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HelpFormatter(click.HelpFormatter):
|
|
9
|
+
def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None:
|
|
10
|
+
match prefix:
|
|
11
|
+
case None:
|
|
12
|
+
prefix = click.style("Usage: ", fg="green", bold=True)
|
|
13
|
+
case _:
|
|
14
|
+
pass
|
|
15
|
+
super().write_usage(prog, args, prefix)
|
|
16
|
+
|
|
17
|
+
def write_heading(self, heading: str) -> None:
|
|
18
|
+
self.write(click.style(f"{heading}:\n", fg="green", bold=True))
|
|
19
|
+
|
|
20
|
+
def write_dl( # type: ignore[override]
|
|
21
|
+
self,
|
|
22
|
+
rows: list[tuple[str, str]],
|
|
23
|
+
col_max: int = 30,
|
|
24
|
+
col_spacing: int = 2,
|
|
25
|
+
) -> None:
|
|
26
|
+
colored_rows = []
|
|
27
|
+
for option_name, help_text in rows:
|
|
28
|
+
colored_option = click.style(option_name, fg="magenta", bold=True)
|
|
29
|
+
colored_rows.append((colored_option, help_text))
|
|
30
|
+
super().write_dl(colored_rows, col_max, col_spacing)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Command(click.Command):
|
|
34
|
+
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
35
|
+
self.format_usage(ctx, formatter)
|
|
36
|
+
self.format_help_text(ctx, formatter)
|
|
37
|
+
self.format_options(ctx, formatter)
|
|
38
|
+
self.format_epilog(ctx, formatter)
|
|
39
|
+
|
|
40
|
+
def get_help(self, ctx: click.Context) -> str:
|
|
41
|
+
formatter = HelpFormatter(width=ctx.terminal_width, max_width=ctx.max_content_width)
|
|
42
|
+
self.format_help(ctx, formatter)
|
|
43
|
+
return formatter.getvalue()
|
uv_upsync/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Module that contains implementation of the exceptions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InvalidDependencySpecifierError(BaseError):
|
|
11
|
+
def __init__(self, dependency_specifier: str) -> None:
|
|
12
|
+
super().__init__(f"Invalid dependency specifier: {dependency_specifier!r}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NoOperatorFoundError(BaseError):
|
|
16
|
+
def __init__(self, dependency_specifier: str) -> None:
|
|
17
|
+
super().__init__(f"No operator found in dependency specifier: {dependency_specifier!r}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MultipleOperatorsFoundError(BaseError):
|
|
21
|
+
def __init__(self, dependency_specifier: str) -> None:
|
|
22
|
+
super().__init__(
|
|
23
|
+
f"Multiple operators found in dependency specifier: {dependency_specifier!r}",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UVCommandError(BaseError):
|
|
28
|
+
def __init__(self, command: list[str], returncode: int, stdout: str, stderr: str) -> None:
|
|
29
|
+
message = f"Command {command!r} returned non-zero exit status {returncode}"
|
|
30
|
+
if stdout:
|
|
31
|
+
message += f"\n\nStdout:\n{stdout}"
|
|
32
|
+
if stderr:
|
|
33
|
+
message += f"\n\nStderr:\n{stderr}"
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
self.command = command
|
|
36
|
+
self.returncode = returncode
|
|
37
|
+
self.stdout = stdout
|
|
38
|
+
self.stderr = stderr
|
uv_upsync/logging.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Module that contains implementation of the logger based on the `click.echo`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SingletonMeta(type):
|
|
11
|
+
_instances: typing.ClassVar[dict[type, object]] = {}
|
|
12
|
+
|
|
13
|
+
def __call__(cls, *args: object, **kwargs: object) -> object:
|
|
14
|
+
if cls not in cls._instances:
|
|
15
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
16
|
+
return cls._instances[cls]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Logger(metaclass=SingletonMeta):
|
|
20
|
+
def info(self, message: str) -> None:
|
|
21
|
+
self._log(click.style(message, fg="green"))
|
|
22
|
+
|
|
23
|
+
def warning(self, message: str) -> None:
|
|
24
|
+
self._log(click.style(message, fg="yellow", dim=True))
|
|
25
|
+
|
|
26
|
+
def exception(self, message: str, exception: Exception) -> None:
|
|
27
|
+
self._log(click.style(message, fg="red", bold=True))
|
|
28
|
+
self._log(click.style(str(exception), fg="red", dim=True))
|
|
29
|
+
|
|
30
|
+
def error(self, message: str) -> None:
|
|
31
|
+
self._log(click.style(message, fg="red", bold=True))
|
|
32
|
+
|
|
33
|
+
def _log(self, message: str) -> None:
|
|
34
|
+
click.echo(message)
|
uv_upsync/parsers.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Module that contains implementation of the TOML parsers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import tomlkit
|
|
8
|
+
|
|
9
|
+
from uv_upsync import exceptions
|
|
10
|
+
from uv_upsync import logging
|
|
11
|
+
from uv_upsync import pypi
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = logging.Logger()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_dependencies_groups(pyproject: tomlkit.TOMLDocument) -> dict[str, list]: # type: ignore[type-arg]
|
|
18
|
+
dependencies_groups = {}
|
|
19
|
+
|
|
20
|
+
project_dependencies = list(pyproject["project"].get("dependencies", [])) # type: ignore[union-attr]
|
|
21
|
+
match project_dependencies:
|
|
22
|
+
case []:
|
|
23
|
+
pass
|
|
24
|
+
case _:
|
|
25
|
+
dependencies_groups.update({"project": project_dependencies})
|
|
26
|
+
|
|
27
|
+
optional_dependencies = dict(pyproject["project"].get("optional-dependencies", {})) # type: ignore[union-attr]
|
|
28
|
+
match optional_dependencies:
|
|
29
|
+
case _ if optional_dependencies:
|
|
30
|
+
dependencies_groups.update({"optional-dependencies": optional_dependencies}) # type: ignore[dict-item]
|
|
31
|
+
|
|
32
|
+
dependency_groups = dict(pyproject.get("dependency-groups", {}))
|
|
33
|
+
match dependency_groups:
|
|
34
|
+
case _ if dependency_groups:
|
|
35
|
+
dependencies_groups.update({"dependency-groups": dependency_groups}) # type: ignore[dict-item]
|
|
36
|
+
|
|
37
|
+
return dependencies_groups
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_dependency_name_and_operator(dependency_specifier: str) -> tuple[str, str]:
|
|
41
|
+
valid_operators = ("===", "==", "~=", ">=", ">", "<=", "<")
|
|
42
|
+
invalid_operators = ("^", "/", ":", "@")
|
|
43
|
+
|
|
44
|
+
# Strip environment markers (everything after semicolon) before parsing
|
|
45
|
+
dependency_part = dependency_specifier.split(";")[0]
|
|
46
|
+
|
|
47
|
+
match any(operator in dependency_part for operator in invalid_operators):
|
|
48
|
+
case True:
|
|
49
|
+
raise exceptions.InvalidDependencySpecifierError(dependency_specifier)
|
|
50
|
+
case False:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
operators = re.findall("|".join(valid_operators), dependency_part)
|
|
54
|
+
match len(operators):
|
|
55
|
+
case 0:
|
|
56
|
+
raise exceptions.NoOperatorFoundError(dependency_specifier)
|
|
57
|
+
case 1:
|
|
58
|
+
operator, *_ = operators
|
|
59
|
+
dependency_name, *_ = dependency_part.replace(" ", "").split(operator)
|
|
60
|
+
return dependency_name.strip(), operator.strip()
|
|
61
|
+
case _:
|
|
62
|
+
raise exceptions.MultipleOperatorsFoundError(dependency_specifier)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def update_dependency_specifier(dependency_specifier: str, exclude: tuple[str, ...]) -> str:
|
|
66
|
+
ignore_operators = ("==", "<=", "<")
|
|
67
|
+
|
|
68
|
+
dependency_name, operator = get_dependency_name_and_operator(dependency_specifier)
|
|
69
|
+
match dependency_name in exclude:
|
|
70
|
+
case True:
|
|
71
|
+
logger.warning(f"Excluding dependency {dependency_name!r}")
|
|
72
|
+
return dependency_specifier
|
|
73
|
+
case False:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
match operator in ignore_operators:
|
|
77
|
+
case True:
|
|
78
|
+
logger.warning(f"Excluding dependency {dependency_name!r}")
|
|
79
|
+
return dependency_specifier
|
|
80
|
+
case False:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
latest_dependency_version = pypi.fetch_latest_dependency_version(dependency_name)
|
|
84
|
+
match latest_dependency_version:
|
|
85
|
+
case None:
|
|
86
|
+
return dependency_specifier
|
|
87
|
+
case _:
|
|
88
|
+
match ";" in dependency_specifier:
|
|
89
|
+
case True:
|
|
90
|
+
after_semi = "".join(dependency_specifier.split(";")[1:])
|
|
91
|
+
updated_dependency_specifier = (
|
|
92
|
+
f"{dependency_name}{operator}{latest_dependency_version};{after_semi}"
|
|
93
|
+
)
|
|
94
|
+
case False:
|
|
95
|
+
updated_dependency_specifier = (
|
|
96
|
+
f"{dependency_name}{operator}{latest_dependency_version}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return updated_dependency_specifier
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def update_dependency_specifiers(
|
|
103
|
+
dependency_specifiers: list[str],
|
|
104
|
+
exclude: tuple[str, ...],
|
|
105
|
+
) -> list[str]:
|
|
106
|
+
updated_dependency_specifiers = []
|
|
107
|
+
for dependency_specifier in dependency_specifiers:
|
|
108
|
+
match any(d_exclude in dependency_specifier for d_exclude in exclude):
|
|
109
|
+
case True:
|
|
110
|
+
logger.warning(f"Skipping {dependency_specifier!r} (excluded)")
|
|
111
|
+
updated_dependency_specifiers.append(dependency_specifier)
|
|
112
|
+
continue
|
|
113
|
+
case False:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
match isinstance(dependency_specifier, tomlkit.items.InlineTable):
|
|
117
|
+
case True:
|
|
118
|
+
logger.warning(f"Skipping inline table: {dependency_specifier!r}")
|
|
119
|
+
updated_dependency_specifiers.append(dependency_specifier)
|
|
120
|
+
continue
|
|
121
|
+
case False:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
get_dependency_name_and_operator(dependency_specifier)
|
|
126
|
+
except exceptions.BaseError as exception:
|
|
127
|
+
logger.exception(f"Skipping inline table: {dependency_specifier!r}", exception)
|
|
128
|
+
updated_dependency_specifiers.append(dependency_specifier)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
updated_dependency_specifier = update_dependency_specifier(dependency_specifier, exclude)
|
|
132
|
+
match updated_dependency_specifier:
|
|
133
|
+
case _ if dependency_specifier != updated_dependency_specifier:
|
|
134
|
+
logger.info(
|
|
135
|
+
f"Updating {dependency_specifier!r} to {updated_dependency_specifier!r}",
|
|
136
|
+
)
|
|
137
|
+
updated_dependency_specifiers.append(updated_dependency_specifier)
|
|
138
|
+
case _:
|
|
139
|
+
logger.warning(f"Skipping {dependency_specifier!r} (no new version available)")
|
|
140
|
+
updated_dependency_specifiers.append(dependency_specifier)
|
|
141
|
+
|
|
142
|
+
return updated_dependency_specifiers
|
uv_upsync/pypi.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Module that contains implementation of the Pypi API client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from uv_upsync import logging
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.Logger()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_dependency_base_name(dependency_name: str) -> str:
|
|
16
|
+
regex_match = re.match(r"^(.*?)\[", dependency_name)
|
|
17
|
+
match regex_match:
|
|
18
|
+
case None:
|
|
19
|
+
return dependency_name.strip()
|
|
20
|
+
case _:
|
|
21
|
+
return regex_match.group(1).strip()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def fetch_latest_dependency_version(dependency_name: str) -> str | None:
|
|
25
|
+
dependency_base_name = get_dependency_base_name(dependency_name)
|
|
26
|
+
response = httpx.get(f"https://pypi.org/pypi/{dependency_base_name}/json")
|
|
27
|
+
try:
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
except httpx.HTTPStatusError as exception:
|
|
30
|
+
logger.exception(f"Failed to fetch latest version for {dependency_name!r}", exception)
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
return response.json()["info"]["version"] # type: ignore[no-any-return]
|
uv_upsync/uv.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Module that contains implementation of the uv commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from uv_upsync import exceptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def lock() -> None:
|
|
11
|
+
try:
|
|
12
|
+
subprocess.run(
|
|
13
|
+
("uv", "lock"),
|
|
14
|
+
check=True,
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
)
|
|
18
|
+
except subprocess.CalledProcessError as exception:
|
|
19
|
+
raise exceptions.UVCommandError(
|
|
20
|
+
command=exception.cmd,
|
|
21
|
+
returncode=exception.returncode,
|
|
22
|
+
stdout=exception.stdout,
|
|
23
|
+
stderr=exception.stderr,
|
|
24
|
+
) from exception
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uv-upsync
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: uv-upsync - is a tool for automated dependency updates and version bumping in pyproject.toml.
|
|
5
|
+
Project-URL: Homepage, https://github.com/pivoshenko/uv-upsync
|
|
6
|
+
Project-URL: Repository, https://github.com/pivoshenko/uv-upsync
|
|
7
|
+
Author-email: Volodymyr Pivoshenko <volodymyr.pivoshenko@gmail.com>
|
|
8
|
+
Maintainer-email: Volodymyr Pivoshenko <volodymyr.pivoshenko@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,cli-tool,cli-tools,cross-platform,dependencies,dependencies-management,dependencies-update,dependencies-upgrade,dependency,hacktoberfest,packaging,packaging-management,packaging-update,packaging-upgrade,plugin,plugins,pypi,python,uv,uv-plugin,uv-plugins
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Other Environment
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering
|
|
27
|
+
Classifier: Topic :: Software Development
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: click>=8.1.8
|
|
32
|
+
Requires-Dist: httpx>=0.28.1
|
|
33
|
+
Requires-Dist: tomlkit>=0.13.3
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
<div align="center">
|
|
37
|
+
<img alt="logo" src="https://github.com/pivoshenko/uv-upsync/blob/main/assets/logo.svg?raw=True" height=200>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
44
|
+
<img alt="License" src="https://img.shields.io/pypi/l/uv-upsync?style=flat-square&logo=opensourceinitiative&logoColor=white&color=0A6847&label=License">
|
|
45
|
+
</a>
|
|
46
|
+
<a href="https://pypi.org/project/uv-upsync">
|
|
47
|
+
<img alt="Python" src="https://img.shields.io/pypi/pyversions/uv-upsync?style=flat-square&logo=python&logoColor=white&color=4856CD&label=Python">
|
|
48
|
+
</a>
|
|
49
|
+
<a href="https://pypi.org/project/uv-upsync">
|
|
50
|
+
<img alt="PyPI" src="https://img.shields.io/pypi/v/uv-upsync?style=flat-square&logo=pypi&logoColor=white&color=4856CD&label=PyPI">
|
|
51
|
+
</a>
|
|
52
|
+
<a href="https://github.com/pivoshenko/uv-upsync/releases">
|
|
53
|
+
<img alt="Release" src="https://img.shields.io/github/v/release/pivoshenko/uv-upsync?style=flat-square&logo=github&logoColor=white&color=4856CD&label=Release">
|
|
54
|
+
</a>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<a href="https://semantic-release.gitbook.io">
|
|
59
|
+
<img alt="Semantic_Release" src="https://img.shields.io/badge/Semantic_Release-angular-e10079?style=flat-square&logo=semanticrelease&logoColor=white&color=D83A56">
|
|
60
|
+
</a>
|
|
61
|
+
<a href="https://pycqa.github.io/isort">
|
|
62
|
+
<img alt="Imports" src="https://img.shields.io/badge/Imports-isort-black.svg?style=flat-square&logo=improvmx&logoColor=white&color=637A9F&">
|
|
63
|
+
</a>
|
|
64
|
+
<a href="https://docs.astral.sh/ruff">
|
|
65
|
+
<img alt="Ruff" src="https://img.shields.io/badge/Style-ruff-black.svg?style=flat-square&logo=ruff&logoColor=white&color=D7FF64">
|
|
66
|
+
</a>
|
|
67
|
+
<a href="https://mypy.readthedocs.io/en/stable/index.html">
|
|
68
|
+
<img alt="mypy" src="https://img.shields.io/badge/mypy-checked-success.svg?style=flat-square&logo=pypy&logoColor=white&color=0A6847">
|
|
69
|
+
</a>
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
<p align="center">
|
|
73
|
+
<a href="https://github.com/pivoshenko/uv-upsync/actions/workflows/tests.yaml">
|
|
74
|
+
<img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/pivoshenko/uv-upsync/tests.yaml?label=Tests&style=flat-square&logo=pytest&logoColor=white&color=0A6847">
|
|
75
|
+
</a>
|
|
76
|
+
<a href="https://github.com/pivoshenko/uv-upsync/actions/workflows/linters.yaml">
|
|
77
|
+
<img alt="Linters" src="https://img.shields.io/github/actions/workflow/status/pivoshenko/uv-upsync/linters.yaml?label=Linters&style=flat-square&logo=lintcode&logoColor=white&color=0A6847">
|
|
78
|
+
</a>
|
|
79
|
+
<a href="https://github.com/pivoshenko/uv-upsync/actions/workflows/release.yaml">
|
|
80
|
+
<img alt="Release" src="https://img.shields.io/github/actions/workflow/status/pivoshenko/uv-upsync/release.yaml?label=Release&style=flat-square&logo=pypi&logoColor=white&color=0A6847">
|
|
81
|
+
</a>
|
|
82
|
+
<a href="https://codecov.io/gh/pivoshenko/uv-upsync" >
|
|
83
|
+
<img alt="Codecov" src="https://img.shields.io/codecov/c/gh/pivoshenko/uv-upsync?token=cqRQxVnDR6&style=flat-square&logo=codecov&logoColor=white&color=0A6847&label=Coverage"/>
|
|
84
|
+
</a>
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<p align="center">
|
|
88
|
+
<a href="https://pypi.org/project/uv-upsync">
|
|
89
|
+
<img alt="Downloads" src="https://img.shields.io/pypi/dm/uv-upsync?style=flat-square&logo=pythonanywhere&logoColor=white&color=4856CD&label=Downloads">
|
|
90
|
+
</a>
|
|
91
|
+
<a href="https://github.com/pivoshenko/uv-upsync">
|
|
92
|
+
<img alt="Stars" src="https://img.shields.io/github/stars/pivoshenko/uv-upsync?style=flat-square&logo=apachespark&logoColor=white&color=4856CD&label=Stars">
|
|
93
|
+
</a>
|
|
94
|
+
</p>
|
|
95
|
+
|
|
96
|
+
<p align="center">
|
|
97
|
+
<a href="https://stand-with-ukraine.pp.ua">
|
|
98
|
+
<img alt="StandWithUkraine" src="https://img.shields.io/badge/Support-Ukraine-FFC93C?style=flat-square&labelColor=07689F">
|
|
99
|
+
</a>
|
|
100
|
+
</p>
|
|
101
|
+
|
|
102
|
+
- [Overview](#overview)
|
|
103
|
+
- [Features](#features)
|
|
104
|
+
- [Installation](#installation)
|
|
105
|
+
- [Usage and Configuration](#usage-and-configuration)
|
|
106
|
+
- [Command-line Options](#command-line-options)
|
|
107
|
+
- [`filepath`](#filepath)
|
|
108
|
+
- [`exclude`](#exclude)
|
|
109
|
+
- [`group`](#group)
|
|
110
|
+
- [`dry-run`](#dry-run)
|
|
111
|
+
- [Examples](#examples)
|
|
112
|
+
- [Excluding specific packages](#excluding-specific-packages)
|
|
113
|
+
- [Updating specific dependency groups](#updating-specific-dependency-groups)
|
|
114
|
+
|
|
115
|
+
## Overview
|
|
116
|
+
|
|
117
|
+
`uv-upsync` - is a tool/plugin for automated dependency updates and version bumping in `pyproject.toml`.
|
|
118
|
+
|
|
119
|
+
### Features
|
|
120
|
+
|
|
121
|
+
- Fully type-safe
|
|
122
|
+
- Automatically updates dependencies to their latest versions from PyPI
|
|
123
|
+
- Multiple dependency groups support - handles `project.dependencies`, `project.optional-dependencies`, and `dependency-groups`
|
|
124
|
+
- Selective group updates - target specific dependency groups for updates (e.g., only update project dependencies or specific optional-dependencies groups)
|
|
125
|
+
- Selective package exclusion - exclude specific packages from being updated
|
|
126
|
+
- Dry-run mode - preview changes without modifying files
|
|
127
|
+
- Safe updates - automatically runs `uv lock` after updates and rolls back on failure
|
|
128
|
+
|
|
129
|
+
## Installation
|
|
130
|
+
|
|
131
|
+
Proceed by installing the tool and running it:
|
|
132
|
+
|
|
133
|
+
```shell
|
|
134
|
+
uvx uv-upsync
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Alternatively, you can add it into your development dependencies:
|
|
138
|
+
|
|
139
|
+
```shell
|
|
140
|
+
uv add --dev uv-upsync
|
|
141
|
+
# or
|
|
142
|
+
uv add uv-upsync --group dev
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Usage and Configuration
|
|
146
|
+
|
|
147
|
+
By default, `uv-upsync` updates all dependencies in the `pyproject.toml`:
|
|
148
|
+
|
|
149
|
+
```shell
|
|
150
|
+
uv-upsync
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Command-line Options
|
|
154
|
+
|
|
155
|
+
#### `filepath`
|
|
156
|
+
|
|
157
|
+
**Type**: `Path`
|
|
158
|
+
|
|
159
|
+
**Default**: `./pyproject.toml`
|
|
160
|
+
|
|
161
|
+
**Short flag**: `-f`
|
|
162
|
+
|
|
163
|
+
Specifies the path to the `pyproject.toml` file. If your project file is located elsewhere or has a different name, you can set this parameter.
|
|
164
|
+
|
|
165
|
+
#### `exclude`
|
|
166
|
+
|
|
167
|
+
**Type**: `str`
|
|
168
|
+
|
|
169
|
+
**Default**: `()`
|
|
170
|
+
|
|
171
|
+
**Multiple values**: `allowed`
|
|
172
|
+
|
|
173
|
+
Specifies packages to exclude from updating. You can provide multiple package names to prevent them from being updated.
|
|
174
|
+
|
|
175
|
+
#### `group`
|
|
176
|
+
|
|
177
|
+
**Type**: `str`
|
|
178
|
+
|
|
179
|
+
**Default**: `()`
|
|
180
|
+
|
|
181
|
+
**Multiple values**: `allowed`
|
|
182
|
+
|
|
183
|
+
Specifies which dependency group(s) to update. You can target specific groups like `project` (for project.dependencies), optional-dependencies names, or dependency-groups names. If not specified, all groups are updated. This is useful when you want to update only certain parts of your dependencies.
|
|
184
|
+
|
|
185
|
+
#### `dry-run`
|
|
186
|
+
|
|
187
|
+
**Type**: `bool`
|
|
188
|
+
|
|
189
|
+
**Default**: `false`
|
|
190
|
+
|
|
191
|
+
Enables preview mode where changes are displayed without modifying the `pyproject.toml` file. This is useful for reviewing what would be updated before applying changes.
|
|
192
|
+
|
|
193
|
+
## Examples
|
|
194
|
+
|
|
195
|
+
### Excluding specific packages
|
|
196
|
+
|
|
197
|
+
```shell
|
|
198
|
+
uv-upsync --exclude click
|
|
199
|
+
|
|
200
|
+
# Skipping 'click>=8.1.8' (excluded)
|
|
201
|
+
# Skipping 'httpx>=0.28.1' (no new version available)
|
|
202
|
+
# Skipping 'tomlkit>=0.13.3' (no new version available)
|
|
203
|
+
# Updating dependencies in 'dependency-groups' group
|
|
204
|
+
# Skipping 'python-semantic-release~=10.4.1' (no new version available)
|
|
205
|
+
# Skipping 'poethepoet>=0.37.0' (no new version available)
|
|
206
|
+
# Skipping 'pyupgrade>=3.21.0' (no new version available)
|
|
207
|
+
# Skipping 'ruff>=0.14.0' (no new version available)
|
|
208
|
+
# Skipping 'commitizen>=4.9.1' (no new version available)
|
|
209
|
+
# Skipping 'mypy>=1.18.2' (no new version available)
|
|
210
|
+
# Skipping 'ruff>=0.14.0' (no new version available)
|
|
211
|
+
# Skipping 'coverage[toml]>=7.10.7' (no new version available)
|
|
212
|
+
# Excluding dependency 'pytest'
|
|
213
|
+
# Skipping 'pytest==7.4.4' (no new version available)
|
|
214
|
+
# Skipping 'pytest-cov>=7.0.0' (no new version available)
|
|
215
|
+
# Skipping 'pytest-lazy-fixture>=0.6.3' (no new version available)
|
|
216
|
+
# Skipping 'pytest-mock>=3.15.1' (no new version available)
|
|
217
|
+
# Skipping 'pytest-sugar>=1.1.1' (no new version available)
|
|
218
|
+
# Skipping 'sh>=2.2.2' (no new version available)
|
|
219
|
+
# Skipping 'xdoctest>=1.3.0' (no new version available)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Updating specific dependency groups
|
|
223
|
+
|
|
224
|
+
```shell
|
|
225
|
+
# Update only project dependencies
|
|
226
|
+
uv-upsync --group project
|
|
227
|
+
|
|
228
|
+
# Update only dev dependencies (assuming you have a 'dev' group)
|
|
229
|
+
uv-upsync --group dev
|
|
230
|
+
|
|
231
|
+
# Update multiple specific groups
|
|
232
|
+
uv-upsync --group project --group test
|
|
233
|
+
|
|
234
|
+
# Skipping 'optional-dependencies.dev' (skipping because not in specified groups)
|
|
235
|
+
# Skipping 'dependency-groups.test' (skipping because not in specified groups)
|
|
236
|
+
# Updating dependencies in 'project' group
|
|
237
|
+
# ...
|
|
238
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
uv_upsync/__init__.py,sha256=jfz1e99qR3cA8gq_kHhfBScA4OZ1hEP2pz7HVNd9iHA,160
|
|
2
|
+
uv_upsync/__main__.py,sha256=2ixZJz7HHmk3MERUL-ZYSSbm49ePf1nZW4PEHbjTN-s,5457
|
|
3
|
+
uv_upsync/commands.py,sha256=wH1v67vEGEZvrxt4ZCoPTClOIrEY5R52tDdQMXfNZxU,1565
|
|
4
|
+
uv_upsync/exceptions.py,sha256=y-pXxnQvKUXVOrPeu00Qxj0EjuwRebee5ghyh6aCVsA,1270
|
|
5
|
+
uv_upsync/logging.py,sha256=F-WD6SzkD8qenTBn3QlSq5JH3v6Ce1HUZU23Nj50R2U,1060
|
|
6
|
+
uv_upsync/parsers.py,sha256=N-XelwagNbbGvqdayAJXXvEMQvUnKVd1HaWIFAyY-G0,5499
|
|
7
|
+
uv_upsync/pypi.py,sha256=LZwRnFl4a017SzzbjSoM6TQvC5TEpCnja4vyv6s7Avo,961
|
|
8
|
+
uv_upsync/uv.py,sha256=4qSOF_wM-rCf-E9v1Ah6ui2Th1EHqzNj1RhdaueH5iU,599
|
|
9
|
+
uv_upsync-1.1.2.dist-info/METADATA,sha256=rMVGJB7AqeIXUEzTmBADhD6AWNaxXefm91MAOfOL1eA,9711
|
|
10
|
+
uv_upsync-1.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
uv_upsync-1.1.2.dist-info/entry_points.txt,sha256=hEEi7yw2CbJAsL0mRcsnxCqssWMNcFCKceqkOjXNgdQ,54
|
|
12
|
+
uv_upsync-1.1.2.dist-info/licenses/LICENSE,sha256=Ea-0VtKpHnE8T-kZEGNNf-Yn3uB50_W4Cw_9k3dX6eQ,1077
|
|
13
|
+
uv_upsync-1.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Volodymyr Pivoshenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|