Skip to content

Commit 0e085e0

Browse files
feat: Add support for poetry 2
1 parent 8c337e8 commit 0e085e0

File tree

12 files changed

+1013
-506
lines changed

12 files changed

+1013
-506
lines changed

.github/workflows/test.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
16+
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
17+
poetry-version: [ '1.8.4', '2.0.0' ]
1718
steps:
1819
- name: Checkout repository
1920
uses: actions/checkout@v4
@@ -31,7 +32,9 @@ jobs:
3132
version: 1.6.0
3233

3334
- name: Install dependencies
34-
run: poetry install
35+
run: |
36+
poetry add poetry==${{ matrix.poetry-version }}
37+
poetry install
3538
3639
- name: Lint
3740
run: |

poetry.lock

Lines changed: 453 additions & 475 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "poetry-plugin-up"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
description = "Poetry plugin that updates dependencies and bumps their versions in pyproject.toml file"
55
authors = ["Mousa Zeid Baker"]
66
packages = [
@@ -25,8 +25,8 @@ classifiers=[
2525
]
2626

2727
[tool.poetry.dependencies]
28-
python = "^3.8"
29-
poetry = "^1.8.4"
28+
python = "^3.9"
29+
poetry = ">=1.8.4, <3.0.0"
3030

3131
[tool.poetry.group.dev.dependencies]
3232
pre-commit = "^2.21.0"

src/poetry_plugin_up/command.py

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from typing import Any, Dict, Iterable, List
22

33
from cleo.helpers import argument, option
4+
from packaging.version import parse as parse_version
5+
from poetry.__version__ import __version__ as poetry_version
46
from poetry.console.commands.installer_command import InstallerCommand
7+
from poetry.core.constraints.version import parse_constraint
58
from poetry.core.packages.dependency import Dependency
69
from poetry.core.packages.dependency_group import DependencyGroup
710
from poetry.core.packages.package import Package
811
from poetry.version.version_selector import VersionSelector
9-
from tomlkit import dumps
12+
from tomlkit import array, dumps, table
1013
from tomlkit.toml_document import TOMLDocument
1114

1215

@@ -102,19 +105,18 @@ def handle(self) -> int:
102105
pyproject_content = self.poetry.file.read()
103106
original_pyproject_content = self.poetry.file.read()
104107

105-
for group in self.get_groups():
106-
for dependency in group.dependencies:
107-
self.handle_dependency(
108-
dependency=dependency,
109-
latest=latest,
110-
pinned=pinned,
111-
only_packages=only_packages,
112-
pyproject_content=pyproject_content,
113-
selector=selector,
114-
exclude=exclude,
115-
exclude_zero_based_caret=exclude_zero_based_caret,
116-
preserve_wildcard=preserve_wildcard,
117-
)
108+
for dependency in self.get_dependencies():
109+
self.handle_dependency(
110+
dependency=dependency,
111+
latest=latest,
112+
pinned=pinned,
113+
only_packages=only_packages,
114+
pyproject_content=pyproject_content,
115+
selector=selector,
116+
exclude=exclude,
117+
exclude_zero_based_caret=exclude_zero_based_caret,
118+
preserve_wildcard=preserve_wildcard,
119+
)
118120

119121
if dry_run:
120122
self.line(dumps(pyproject_content))
@@ -127,7 +129,10 @@ def handle(self) -> int:
127129
try:
128130
if no_install:
129131
# update lock file
130-
self.call(name="lock")
132+
if poetry_version_above_2():
133+
self.call(name="lock", args="--regenerate")
134+
else:
135+
self.call(name="lock")
131136
else:
132137
# update dependencies
133138
self.call(name="update")
@@ -137,11 +142,10 @@ def handle(self) -> int:
137142
raise e
138143
return 0
139144

140-
def get_groups(self) -> Iterable[DependencyGroup]:
145+
def get_dependencies(self) -> Iterable[DependencyGroup]:
141146
"""Returns activated dependency groups"""
142-
143147
for group in self.activated_groups:
144-
yield self.poetry.package.dependency_group(group)
148+
yield from self.poetry.package.dependency_group(group).dependencies
145149

146150
def handle_dependency(
147151
self,
@@ -156,7 +160,6 @@ def handle_dependency(
156160
preserve_wildcard: bool,
157161
) -> None:
158162
"""Handles a dependency"""
159-
160163
if not self.is_bumpable(
161164
dependency,
162165
only_packages,
@@ -187,7 +190,10 @@ def handle_dependency(
187190
and "." in dependency.pretty_constraint
188191
):
189192
new_version = "~" + candidate.pretty_version
190-
elif dependency.pretty_constraint[:2] == ">=":
193+
elif (
194+
dependency.pretty_constraint[:2] == ">="
195+
and len(dependency.pretty_constraint.split(",")) == 1
196+
):
191197
new_version = ">=" + candidate.pretty_version
192198
else:
193199
new_version = "^" + candidate.pretty_version
@@ -209,7 +215,6 @@ def is_bumpable(
209215
preserve_wildcard: bool,
210216
) -> bool:
211217
"""Determines if a dependency can be bumped in pyproject.toml"""
212-
213218
if dependency.source_type in ["git", "file", "directory"]:
214219
return False
215220
if dependency.name in ["python"]:
@@ -265,8 +270,35 @@ def bump_version_in_pyproject_content(
265270
pyproject_content: TOMLDocument,
266271
) -> None:
267272
"""Bumps versions in pyproject content (pyproject.toml)"""
268-
269-
poetry_content: Dict[str, Any] = pyproject_content["tool"]["poetry"]
273+
project_content = pyproject_content.get("project", table())
274+
if uses_pep508_style(pyproject_content):
275+
for group in dependency.groups:
276+
sections = []
277+
if group == "main":
278+
sections.append(
279+
project_content.get("dependencies", array())
280+
)
281+
opt_deps = project_content.get("optional-dependencies", {})
282+
for section in opt_deps.values():
283+
sections.append(section)
284+
elif group in (
285+
dep_groups := pyproject_content.get("dependency-groups", {})
286+
):
287+
sections.append(dep_groups.get(group, array()))
288+
for section in sections:
289+
for index, entry in enumerate(section):
290+
entry_dependency = Dependency.create_from_pep_508(entry)
291+
if (
292+
entry_dependency.name == dependency.name
293+
and entry_dependency.constraint
294+
!= parse_constraint(new_version)
295+
):
296+
entry_dependency.constraint = new_version
297+
section[index] = entry_dependency.to_pep_508()
298+
299+
poetry_content: Dict[str, Any] = pyproject_content.get("tool", {}).get(
300+
"poetry", {}
301+
)
270302

271303
for group in dependency.groups:
272304
# find section to modify
@@ -293,3 +325,16 @@ def bump_version_in_pyproject_content(
293325
def is_pinned(version: str) -> bool:
294326
"""Returns if `version` is an exact version."""
295327
return version[0].isdigit() or version[:2] == "=="
328+
329+
330+
def uses_pep508_style(pyproject_content: TOMLDocument) -> bool:
331+
project_content = pyproject_content.get("project", table())
332+
return (
333+
"dependencies" in project_content
334+
or "optional-dependencies" in project_content
335+
or "dependency-groups" in pyproject_content
336+
)
337+
338+
339+
def poetry_version_above_2() -> bool:
340+
return parse_version(poetry_version) >= parse_version("2.0.0")

tests/conftest.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@
99
from poetry.poetry import Poetry
1010
from poetry.pyproject.toml import PyProjectTOML
1111
from pytest import TempPathFactory
12+
from pytest_mock import MockerFixture
1213
from tomlkit.toml_document import TOMLDocument
1314

1415
from tests.helpers import TestApplication, TestUpCommand
1516

1617

18+
@pytest.fixture
19+
def patch_path_is_file(mocker: MockerFixture):
20+
mocker.patch(
21+
"poetry.core.packages.dependency.Path.is_file", return_value=True
22+
)
23+
yield
24+
25+
1726
@pytest.fixture(scope="function")
1827
def project_path() -> Path:
1928
return Path(__file__).parent / "fixtures" / "simple_project"
@@ -39,7 +48,7 @@ def tmp_pyproject_path(
3948
tmp_pyproject_path = (
4049
tmp_path_factory.mktemp("simple_project") / "pyproject.toml"
4150
)
42-
tmp_pyproject_path.write_text(pyproject_content.as_string())
51+
tmp_pyproject_path.write_text(pyproject_content.as_string(), newline="\n")
4352
return tmp_pyproject_path
4453

4554

@@ -50,6 +59,38 @@ def app_tester(tmp_pyproject_path: Path) -> ApplicationTester:
5059
return ApplicationTester(app)
5160

5261

62+
@pytest.fixture(scope="function")
63+
def project_path_v2() -> Path:
64+
return Path(__file__).parent / "fixtures" / "simple_project_poetry_v2"
65+
66+
67+
@pytest.fixture(scope="function")
68+
def pyproject_content_v2(project_path_v2: Path) -> TOMLDocument:
69+
path = project_path_v2 / "pyproject.toml"
70+
return PyProjectTOML(path).file.read()
71+
72+
73+
@pytest.fixture(scope="function")
74+
def tmp_pyproject_path_v2(
75+
tmp_path_factory: TempPathFactory,
76+
pyproject_content_v2: TOMLDocument,
77+
) -> Path:
78+
tmp_pyproject_path = (
79+
tmp_path_factory.mktemp("simple_project_poetry_v2") / "pyproject.toml"
80+
)
81+
tmp_pyproject_path.write_text(
82+
pyproject_content_v2.as_string(), newline="\n"
83+
)
84+
return tmp_pyproject_path
85+
86+
87+
@pytest.fixture(scope="function")
88+
def app_tester_v2(tmp_pyproject_path_v2: Path) -> ApplicationTester:
89+
poetry = Factory().create_poetry(tmp_pyproject_path_v2)
90+
app = TestApplication(poetry)
91+
return ApplicationTester(app)
92+
93+
5394
@pytest.fixture
5495
def poetry(project_path: Path) -> Poetry:
5596
return Factory().create_poetry(project_path)

0 commit comments

Comments
 (0)