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 ADDED
@@ -0,0 +1,6 @@
1
+ """uv-upsync - is a tool for automated dependency updates and version bumping in pyproject.toml."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ __version__ = "1.1.2"
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()
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ uv-upsync = uv_upsync.__main__:main
@@ -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.