Skip to content

Commit

Permalink
Merge pull request #73 from JanEricNitschke/master
Browse files Browse the repository at this point in the history
feat: Add support for poetry 2
  • Loading branch information
MousaZeidBaker authored Jan 15, 2025
2 parents 8c337e8 + 7b7955a commit 03dfd63
Show file tree
Hide file tree
Showing 12 changed files with 1,021 additions and 505 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
poetry-version: [ '1.8.4', '2.0.0' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -31,7 +32,9 @@ jobs:
version: 1.6.0

- name: Install dependencies
run: poetry install
run: |
poetry add poetry==${{ matrix.poetry-version }}
poetry install
- name: Lint
run: |
Expand Down
928 changes: 453 additions & 475 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ classifiers=[
]

[tool.poetry.dependencies]
python = "^3.8"
poetry = "^1.8.4"
python = "^3.9"
poetry = ">=1.8.4, <3.0.0"

[tool.poetry.group.dev.dependencies]
pre-commit = "^2.21.0"
Expand Down
91 changes: 68 additions & 23 deletions src/poetry_plugin_up/command.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import Any, Dict, Iterable, List

from cleo.helpers import argument, option
from packaging.version import parse as parse_version
from poetry.__version__ import __version__ as poetry_version
from poetry.console.commands.installer_command import InstallerCommand
from poetry.core.constraints.version import parse_constraint
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.dependency_group import DependencyGroup
from poetry.core.packages.package import Package
from poetry.version.version_selector import VersionSelector
from tomlkit import dumps
from tomlkit import array, dumps, table
from tomlkit.toml_document import TOMLDocument


Expand Down Expand Up @@ -102,19 +105,18 @@ def handle(self) -> int:
pyproject_content = self.poetry.file.read()
original_pyproject_content = self.poetry.file.read()

for group in self.get_groups():
for dependency in group.dependencies:
self.handle_dependency(
dependency=dependency,
latest=latest,
pinned=pinned,
only_packages=only_packages,
pyproject_content=pyproject_content,
selector=selector,
exclude=exclude,
exclude_zero_based_caret=exclude_zero_based_caret,
preserve_wildcard=preserve_wildcard,
)
for dependency in self.get_dependencies():
self.handle_dependency(
dependency=dependency,
latest=latest,
pinned=pinned,
only_packages=only_packages,
pyproject_content=pyproject_content,
selector=selector,
exclude=exclude,
exclude_zero_based_caret=exclude_zero_based_caret,
preserve_wildcard=preserve_wildcard,
)

if dry_run:
self.line(dumps(pyproject_content))
Expand All @@ -127,7 +129,10 @@ def handle(self) -> int:
try:
if no_install:
# update lock file
self.call(name="lock")
if poetry_version_above_2():
self.call(name="lock", args="--regenerate")
else:
self.call(name="lock")
else:
# update dependencies
self.call(name="update")
Expand All @@ -137,11 +142,10 @@ def handle(self) -> int:
raise e
return 0

def get_groups(self) -> Iterable[DependencyGroup]:
def get_dependencies(self) -> Iterable[DependencyGroup]:
"""Returns activated dependency groups"""

for group in self.activated_groups:
yield self.poetry.package.dependency_group(group)
yield from self.poetry.package.dependency_group(group).dependencies

def handle_dependency(
self,
Expand All @@ -156,7 +160,6 @@ def handle_dependency(
preserve_wildcard: bool,
) -> None:
"""Handles a dependency"""

if not self.is_bumpable(
dependency,
only_packages,
Expand Down Expand Up @@ -187,7 +190,10 @@ def handle_dependency(
and "." in dependency.pretty_constraint
):
new_version = "~" + candidate.pretty_version
elif dependency.pretty_constraint[:2] == ">=":
elif (
dependency.pretty_constraint[:2] == ">="
and len(dependency.pretty_constraint.split(",")) == 1
):
new_version = ">=" + candidate.pretty_version
else:
new_version = "^" + candidate.pretty_version
Expand All @@ -209,7 +215,6 @@ def is_bumpable(
preserve_wildcard: bool,
) -> bool:
"""Determines if a dependency can be bumped in pyproject.toml"""

if dependency.source_type in ["git", "file", "directory"]:
return False
if dependency.name in ["python"]:
Expand Down Expand Up @@ -265,8 +270,35 @@ def bump_version_in_pyproject_content(
pyproject_content: TOMLDocument,
) -> None:
"""Bumps versions in pyproject content (pyproject.toml)"""

poetry_content: Dict[str, Any] = pyproject_content["tool"]["poetry"]
project_content = pyproject_content.get("project", table())
if uses_pep508_style(pyproject_content):
for group in dependency.groups:
sections = []
if group == "main":
sections.append(
project_content.get("dependencies", array())
)
opt_deps = project_content.get("optional-dependencies", {})
for section in opt_deps.values():
sections.append(section)
elif group in (
dep_groups := pyproject_content.get("dependency-groups", {})
):
sections.append(dep_groups.get(group, array()))
for section in sections:
for index, entry in enumerate(section):
entry_dependency = Dependency.create_from_pep_508(entry)
if (
entry_dependency.name == dependency.name
and entry_dependency.constraint
!= parse_constraint(new_version)
):
entry_dependency.constraint = new_version
section[index] = entry_dependency.to_pep_508()

poetry_content: Dict[str, Any] = pyproject_content.get("tool", {}).get(
"poetry", {}
)

for group in dependency.groups:
# find section to modify
Expand All @@ -293,3 +325,16 @@ def bump_version_in_pyproject_content(
def is_pinned(version: str) -> bool:
"""Returns if `version` is an exact version."""
return version[0].isdigit() or version[:2] == "=="


def uses_pep508_style(pyproject_content: TOMLDocument) -> bool:
project_content = pyproject_content.get("project", table())
return (
"dependencies" in project_content
or "optional-dependencies" in project_content
or "dependency-groups" in pyproject_content
)


def poetry_version_above_2() -> bool:
return parse_version(poetry_version) >= parse_version("2.0.0")
52 changes: 51 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from pathlib import Path
from typing import List

Expand All @@ -9,11 +10,20 @@
from poetry.poetry import Poetry
from poetry.pyproject.toml import PyProjectTOML
from pytest import TempPathFactory
from pytest_mock import MockerFixture
from tomlkit.toml_document import TOMLDocument

from tests.helpers import TestApplication, TestUpCommand


@pytest.fixture
def patch_path_is_file(mocker: MockerFixture):
mocker.patch(
"poetry.core.packages.dependency.Path.is_file", return_value=True
)
yield


@pytest.fixture(scope="function")
def project_path() -> Path:
return Path(__file__).parent / "fixtures" / "simple_project"
Expand All @@ -39,7 +49,12 @@ def tmp_pyproject_path(
tmp_pyproject_path = (
tmp_path_factory.mktemp("simple_project") / "pyproject.toml"
)
tmp_pyproject_path.write_text(pyproject_content.as_string())
if sys.version_info >= (3, 10):
tmp_pyproject_path.write_text(
pyproject_content.as_string(), newline="\n"
)
else:
tmp_pyproject_path.write_text(pyproject_content.as_string())
return tmp_pyproject_path


Expand All @@ -50,6 +65,41 @@ def app_tester(tmp_pyproject_path: Path) -> ApplicationTester:
return ApplicationTester(app)


@pytest.fixture(scope="function")
def project_path_v2() -> Path:
return Path(__file__).parent / "fixtures" / "simple_project_poetry_v2"


@pytest.fixture(scope="function")
def pyproject_content_v2(project_path_v2: Path) -> TOMLDocument:
path = project_path_v2 / "pyproject.toml"
return PyProjectTOML(path).file.read()


@pytest.fixture(scope="function")
def tmp_pyproject_path_v2(
tmp_path_factory: TempPathFactory,
pyproject_content_v2: TOMLDocument,
) -> Path:
tmp_pyproject_path = (
tmp_path_factory.mktemp("simple_project_poetry_v2") / "pyproject.toml"
)
if sys.version_info >= (3, 10):
tmp_pyproject_path.write_text(
pyproject_content_v2.as_string(), newline="\n"
)
else:
tmp_pyproject_path.write_text(pyproject_content_v2.as_string())
return tmp_pyproject_path


@pytest.fixture(scope="function")
def app_tester_v2(tmp_pyproject_path_v2: Path) -> ApplicationTester:
poetry = Factory().create_poetry(tmp_pyproject_path_v2)
app = TestApplication(poetry)
return ApplicationTester(app)


@pytest.fixture
def poetry(project_path: Path) -> Poetry:
return Factory().create_poetry(project_path)
Expand Down
Loading

0 comments on commit 03dfd63

Please sign in to comment.