Skip to content
This repository has been archived by the owner on Dec 18, 2022. It is now read-only.

Commit

Permalink
Merge pull request #42 from MousaZeidBaker/refactor/structure
Browse files Browse the repository at this point in the history
Refactor project structure and classes
  • Loading branch information
MousaZeidBaker authored May 8, 2022
2 parents c17ae5a + ac0eb06 commit dd47cfa
Show file tree
Hide file tree
Showing 21 changed files with 277 additions and 812 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "poetryup"
version = "0.6.2"
version = "0.6.3"
description = "Update dependencies and bump their version in the pyproject.toml file"
authors = ["Mousa Zeid Baker"]
packages = [
Expand Down
165 changes: 97 additions & 68 deletions src/poetryup/pyproject.py → src/poetryup/core/pyproject.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,12 @@
import logging
import re
import subprocess
from dataclasses import dataclass
from typing import List, Optional, Union
from typing import Dict, List, Optional, Union

import tomlkit
from packaging import version as version_
from tomlkit import items


@dataclass
class Dependency:
"""A class to represent a dependency"""

name: str
version: Union[items.String, items.InlineTable, items.Array]
group: str

@property
def normalized_name(self) -> str:
# https://www.python.org/dev/peps/pep-0503/#normalized-names
return self.name.replace("_", "-").lower()

@property
def constraint(self) -> str:
if type(self.version) is items.String:
if self.version[0].startswith(("^", "~")):
return self.version[0]
elif type(self.version) is items.InlineTable:
if self.version.get("version", "").startswith(("^", "~")):
return self.version["version"][0]
return "" # dependencies with exact version or multiple versions
from poetryup.models.dependency import Dependency


class Pyproject:
Expand All @@ -47,14 +23,15 @@ class Pyproject:
def __init__(self, pyproject_str: str) -> None:
self.pyproject = tomlkit.loads(pyproject_str)
self.poetry_version = version_.parse(self.__get_poetry_version())
self._dependencies = None # caches the dependencies

def dumps(self) -> str:
"""Dumps pyproject into a string."""

return tomlkit.dumps(self.pyproject)
@property
def dependencies(self) -> List[Dependency]:
"""The pyproject dependencies"""

def list_dependencies(self) -> List[Dependency]:
"""Returns pyproject dependencies"""
if self._dependencies is not None:
# return cached dependencies
return self._dependencies

dependencies: List[Dependency] = []
table = self.pyproject["tool"]["poetry"]
Expand Down Expand Up @@ -90,53 +67,105 @@ def list_dependencies(self) -> List[Dependency]:
)
dependencies.append(dependency)

self._dependencies = dependencies # cache dependencies
return dependencies

def list_lock_dependencies(self) -> List[Dependency]:
"""Returns pyproject dependencies with their lock version"""
@property
def lock_dependencies(self) -> List[Dependency]:
"""The pyproject dependencies with their lock version"""

# create list of lock dependencies
# run poetry show to get currently installed dependencies
output = self.__run_poetry_show()

# create dependencies from each line of the output
pattern = re.compile("^[a-zA-Z-]+")
lock_deps: List[Dependency] = []
lock_dependencies: List[Dependency] = []
for line in output.split("\n"):
if pattern.match(line) is not None:
name, version, *_ = line.split()
dependency = Dependency(
name=name,
version=version,
group="",
if pattern.match(line) is None:
# not a matching line, continue to next
continue

# extract name and version
lock_name, lock_version, *_ = line.split()

# search for dependency in pyproject
dependency = self.search_dependency(self.dependencies, lock_name)
if dependency is None:
# dependency not found, continue to next
continue

lock_dependencies.append(
Dependency(
name=dependency.name,
version=lock_version,
group=dependency.group,
)
lock_deps.append(dependency)
)

# list dependencies from pyproject and set version to lock version
dependencies = self.list_dependencies()
for dependency in dependencies:
lock_dep = next(
(
lock_dep
for lock_dep in lock_deps
if lock_dep.normalized_name == dependency.normalized_name
),
None,
return lock_dependencies

@property
def bumped_dependencies(self) -> List[Dependency]:
"""The pyproject dependencies with their version bumped to lock version
Lock versions will be used if applicable. For instance, using the lock
version for a dependency that is specified with the inequality
constraint '!=x.y.z' would completely change its meaning.
"""

lock_dependencies = self.lock_dependencies

bumped_dependencies: List[Dependency] = []
for dependency in self.dependencies:
# search for lock dependency
lock_dependency = self.search_dependency(
lock_dependencies,
dependency.name,
)
if lock_dep is None:
logging.info(
f"Couldn't find lock dependency for '{dependency.name}'"
)
continue

if type(dependency.version) is items.String:
dependency.version = dependency.constraint + lock_dep.version
version = dependency.version
if isinstance(version, str):
version = dependency.constraint + lock_dependency.version
elif (
type(dependency.version) is items.InlineTable
and dependency.version.get("version") is not None
isinstance(version, Dict) and version.get("version") is not None
):
dependency.version["version"] = (
dependency.constraint + lock_dep.version
version["version"] = (
dependency.constraint + lock_dependency.version
)

return dependencies
bumped_dependencies.append(
Dependency(
name=dependency.name,
version=version,
group=dependency.group,
)
)

return bumped_dependencies

def dumps(self) -> str:
"""Dumps pyproject into a string."""

return tomlkit.dumps(self.pyproject)

def search_dependency(
self,
dependencies: List[Dependency],
name: str,
) -> Union[Dependency, None]:
"""Search for a dependency by name given a list of dependencies
Args:
dependencies: A list of dependencies to search in
name: Name of the dependency to search for
Returns:
A dependency if found, None if not found
"""

for dependency in dependencies:
if dependency.name == name or dependency.normalized_name == name:
return dependency

def update_dependencies(
self,
Expand All @@ -156,11 +185,11 @@ def update_dependencies(
# to avoid version solver error in case dependencies depend on each
# other
groups = {}
for dependency in self.list_dependencies():
for dependency in self.dependencies:
if skip_exact and dependency.constraint == "":
# skip dependencies with an exact version
continue
if type(dependency.version) is items.String:
if isinstance(dependency.version, str):
groups[dependency.group] = groups.get(
dependency.group, []
) + [f"{dependency.name}@latest"]
Expand All @@ -176,7 +205,7 @@ def update_dependencies(

# bump versions in pyproject
table = self.pyproject["tool"]["poetry"]
for dependency in self.list_lock_dependencies():
for dependency in self.bumped_dependencies:
if dependency.group == "default":
table["dependencies"][dependency.name] = dependency.version
elif (
Expand Down
2 changes: 1 addition & 1 deletion src/poetryup/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import typer

from poetryup.pyproject import Pyproject
from poetryup.core.pyproject import Pyproject

logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO").upper())

Expand Down
32 changes: 32 additions & 0 deletions src/poetryup/models/dependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from dataclasses import dataclass
from typing import Dict, List, Union


@dataclass(frozen=True)
class Dependency:
"""A class to represent a dependency
Args:
name: The name of the dependency
version: The version of the dependency
group: The group of the dependency
"""

name: str
version: Union[str, Dict, List]
group: str

@property
def normalized_name(self) -> str:
# https://www.python.org/dev/peps/pep-0503/#normalized-names
return self.name.replace("_", "-").lower()

@property
def constraint(self) -> str:
if isinstance(self.version, str):
if self.version[0].startswith(("^", "~")):
return self.version[0]
elif isinstance(self.version, Dict):
if self.version.get("version", "").startswith(("^", "~")):
return self.version["version"][0]
return "" # dependencies with exact version or multiple versions
24 changes: 21 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from pytest_mock import MockerFixture

from poetryup.pyproject import Pyproject
from poetryup.core.pyproject import Pyproject


@pytest.fixture(scope="function")
Expand All @@ -18,10 +18,28 @@ def mock_poetry_commands(mocker: MockerFixture) -> None:
Pyproject,
"_Pyproject__run_poetry_show",
return_value=(
"poetryup 0.2.0 Update dependencies and bump their version in the "
"poetryup 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-extra 0.2.0 "
"poetryup-caret 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-tilde 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-exact 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-restricted 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-git 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-underscore 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
"poetryup-capital 0.2.0 "
"pyproject.toml file"
"\n└── toml >=0.10.2,<0.11.0\n"
),
Expand Down
11 changes: 10 additions & 1 deletion tests/unit/fixtures/expected_pyproject/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ include = ["LICENSE"]
python = "^3.6"
poetryup = "^0.2.0"

[tool.poetry.dev-dependencies]
[tool.poetry.group.main.dependencies]
poetryup_caret = "^0.2.0"
poetryup_tilde = "~0.2.0"
poetryup_exact = "0.2.0"
poetryup_restricted = { version = "^0.2.0", python = "<3.7" }
poetryup_git = { git = "https://github.com/MousaZeidBaker/poetryup.git" }
poetryup_underscore = "^0.2.0"
Poetryup_Capital = "^0.2.0"

[tool.poetry.group.dev.dependencies]

[tool.poetry.scripts]
poetryup = "src.test_poetryup.test_poetryup:main"
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit dd47cfa

Please sign in to comment.