Skip to content

Commit

Permalink
normalize package names
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Avrahami committed May 12, 2024
1 parent 48b7bfa commit 760eabb
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 43 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# poetry-stale-dependencies changelog
## 0.2.1
### Added
* console messages are now colored
### Changed
* non-stale dependencies are now only shown in very-verbose mode
### Fixed
* package names are now normalized in accordance with pypi standards
* fixed some typos in the README

## 0.2.0
### Changed
* the `include_remote_prereleases` was renamed to `include_prereleases`
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ sources = [
"pypi",
"my_custom_source" # note that currently only unauthenticated sources are supported
]
time_to_stale = "1mo" # dependencies that default is 2 weeks
time_to_stale = "1mo" # dependencies that have at lest this gap between their latest release and
# the installed release will be marked as satle, default is 2 weeks
time_to_ripe = "1d" # releases that are less than 1 day old will not be considered, default is 3 days
# we can also have per-package configurations
[tool.stale-dependencies.packages]
Expand Down
7 changes: 4 additions & 3 deletions poetry_stale_dependencies/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from poetry_stale_dependencies.inspections import PackageInspectSpecs
from poetry_stale_dependencies.lock_spec import LegacyPackageSource, PackageSpec
from poetry_stale_dependencies.util import PackageName, to_package_name


def parse_timedelta(v: Any) -> timedelta:
Expand Down Expand Up @@ -65,15 +66,15 @@ def from_raw(cls, raw: dict[str, Any]) -> PackageConfig:
class Config:
lockfile: str
sources: Sequence[str]
packages: Mapping[str, PackageConfig]
packages: Mapping[PackageName, PackageConfig]
time_to_stale: timedelta
time_to_ripe: timedelta

@classmethod
def from_raw(cls, raw: dict[str, Any]) -> Config:
packages = {}
for package, package_config in raw.get("packages", {}).items():
packages[package] = PackageConfig.from_raw(package_config)
packages[to_package_name(package)] = PackageConfig.from_raw(package_config)

return cls(
lockfile=raw.get("lockfile", "poetry.lock"),
Expand All @@ -86,7 +87,7 @@ def from_raw(cls, raw: dict[str, Any]) -> Config:
def lockfile_path(self) -> Path:
return Path(self.lockfile)

def inspect_specs(self, package: str, specs: Sequence[PackageSpec]) -> Iterator[PackageInspectSpecs]:
def inspect_specs(self, package: PackageName, specs: Sequence[PackageSpec]) -> Iterator[PackageInspectSpecs]:
package_config = self.packages.get(package) or PackageConfig.Default
if package_config.ignore:
return None
Expand Down
22 changes: 13 additions & 9 deletions poetry_stale_dependencies/inspections.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from poetry_stale_dependencies.lock_spec import LegacyPackageSource, LockSpec, PackageDependency, unknown_marker
from poetry_stale_dependencies.project_spec import ProjectDependency, ProjectSpec
from poetry_stale_dependencies.remote import pull_remote_specs
from poetry_stale_dependencies.util import render_timedelta
from poetry_stale_dependencies.util import PackageName, render_timedelta


@dataclass
class PackageInspectSpecs:
package: str
package: PackageName
source: LegacyPackageSource | None
time_to_stale: timedelta
time_to_ripe: timedelta
Expand Down Expand Up @@ -108,8 +108,8 @@ class NonStalePackageInspectResults:

def writelines(self, com: Command):
com.line(
f"{self.package} [{self.source.reference}]: Package is up to date ({self.local_version.version})",
verbosity=Verbosity.VERBOSE,
f"<info>{self.package} [{self.source.reference}]</>: Package is up to date (<comment>{self.local_version.version}</>)",
verbosity=Verbosity.VERY_VERBOSE,
)
if self.latest_version.version == self.local_version.version:
com.line(f"\t{self.local_version.version} is latest", verbosity=Verbosity.VERY_VERBOSE)
Expand All @@ -132,24 +132,28 @@ class StalePackageInspectResults:
def writelines(self, com: Command):
delta = self.latest_version.time - self.local_version.time
com.line(
f"{self.package} [{self.source.reference}]: local version {self.local_version.version} is stale, latest is {self.latest_version.version} (delta: {render_timedelta(delta)})"
f"<info>{self.package} [{self.source.reference}]</>: local version <comment>{self.local_version.version}</> is stale, latest is <comment>{self.latest_version.version}</> (delta: <info>{render_timedelta(delta)}</>)"
)
com.line(
f"\t{self.local_version.version} was uploaded at {self.local_version.time.isoformat()}, {self.latest_version.version} was uploaded at {self.latest_version.time.isoformat()}",
f"\t<comment>{self.local_version.version}</> was uploaded at <comment>{self.local_version.time.isoformat()}</>, <comment>{self.latest_version.version}</> was uploaded at <comment>{self.latest_version.time.isoformat()}</>",
verbosity=Verbosity.VERBOSE,
)
if self.oldest_non_stale is not None:
com.line(
f"\toldest non-stale release is {self.oldest_non_stale.version} ({self.oldest_non_stale.time.isoformat()})",
f"\toldest non-stale release is <comment>{self.oldest_non_stale.version}</> (<comment>{self.oldest_non_stale.time.isoformat()}</>)",
verbosity=Verbosity.VERBOSE,
)
if self.dependencies:
com.line(f"\tused by {len(self.dependencies)}:", verbosity=Verbosity.VERBOSE)
use_plural = "usages" if len(self.dependencies) > 1 else "usage"
com.line(f"\tfound {len(self.dependencies)} {use_plural}:", verbosity=Verbosity.VERBOSE)
for package_name, dep in self.dependencies:
if dep.marker is None:
marker_desc = ""
elif dep.marker is unknown_marker:
marker_desc = " [unknown marker]"
else:
marker_desc = f" [{dep.marker}]"
com.line(f"\t\t{package_name}: {dep.version_req}{marker_desc}", verbosity=Verbosity.VERBOSE)
com.line(
f"\t\t<info>{package_name}</>: {dep.version_req}<comment>{marker_desc}</>",
verbosity=Verbosity.VERBOSE,
)
23 changes: 13 additions & 10 deletions poetry_stale_dependencies/lock_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from cleo.commands.command import Command
from cleo.io.outputs.output import Verbosity

from poetry_stale_dependencies.util import PackageName, to_package_name


@dataclass
class LegacyPackageSource:
Expand Down Expand Up @@ -58,16 +60,16 @@ def from_raw(cls, raw: Any) -> PackageDependency | None:
class PackageSpec:
version: str
source: LegacyPackageSource | None
dependencies: Mapping[str, PackageDependency]
dependencies: Mapping[PackageName, PackageDependency]


@dataclass
class LockSpec:
packages: dict[str, list[PackageSpec]] = field(default_factory=dict)
packages: dict[PackageName, list[PackageSpec]] = field(default_factory=dict)

def get_packages(
self, packages_to_inspect: Sequence[str], com: Command
) -> Iterator[tuple[str, Sequence[PackageSpec]]]:
self, packages_to_inspect: Sequence[PackageName], com: Command
) -> Iterator[tuple[PackageName, Sequence[PackageSpec]]]:
if packages_to_inspect:
for package in packages_to_inspect:
if specs := self.packages.get(package):
Expand All @@ -92,17 +94,18 @@ def from_raw(cls, raw: dict[str, Any], com: Command) -> LockSpec:

@classmethod
def from_raw_v2(cls, raw: dict[str, Any], com: Command) -> LockSpec:
packages: dict[str, list[PackageSpec]] = {}
packages: dict[PackageName, list[PackageSpec]] = {}
for package in raw.get("package", ()):
name = package.get("name")
raw_name = package.get("name")
version = package.get("version")
raw_source = package.get("source")
if name is None or version is None:
if raw_name is None or version is None:
com.line_error(
f"Package missing name or version, package ({name=}, {version=}) will be ignored",
f"Package missing name or version, package ({raw_name=}, {version=}) will be ignored",
verbosity=Verbosity.NORMAL,
)
continue
raw_source = package.get("source")
name = to_package_name(raw_name)
if raw_source is None:
source = None
elif raw_source.get("type") != "legacy":
Expand All @@ -129,5 +132,5 @@ def from_raw_v2(cls, raw: dict[str, Any], com: Command) -> LockSpec:
verbosity=Verbosity.NORMAL,
)

packages.setdefault(name, []).append(PackageSpec(version, source, dependencies))
packages.setdefault(to_package_name(name), []).append(PackageSpec(version, source, dependencies))
return cls(packages)
4 changes: 3 additions & 1 deletion poetry_stale_dependencies/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from poetry_stale_dependencies.lock_spec import LockSpec
from poetry_stale_dependencies.project_spec import ProjectSpec
from poetry_stale_dependencies.util import to_package_name


class ShowStaleCommand(Command):
Expand Down Expand Up @@ -76,7 +77,8 @@ def _get_config(self, pyproject: dict, time_to_stale: timedelta | None, time_to_
return ret

def handle(self) -> int:
packages_whitelist: list[str] = self.argument("packages")
raw_packages_whitelist: list[str] = self.argument("packages")
packages_whitelist = [to_package_name(package) for package in raw_packages_whitelist]
project_path: str = self.option("project_path")
raw_workers = self.option("multi_threading_workers")
n_workers: int | None
Expand Down
7 changes: 4 additions & 3 deletions poetry_stale_dependencies/project_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from cleo.commands.command import Command

from poetry_stale_dependencies.lock_spec import UnkownMarker, unknown_marker
from poetry_stale_dependencies.util import PackageName, to_package_name


@dataclass
Expand Down Expand Up @@ -44,7 +45,7 @@ def from_raw(cls, raw: Any) -> ProjectDependency | None:
@dataclass
class ProjectSpec:
name: str = "root"
dependencies_groups: dict[str, dict[str, list[ProjectDependency]]] = field(default_factory=dict)
dependencies_groups: dict[str, dict[PackageName, list[ProjectDependency]]] = field(default_factory=dict)

@classmethod
def from_raw(cls, raw: dict[str, Any], com: Command) -> ProjectSpec:
Expand All @@ -53,7 +54,7 @@ def from_raw(cls, raw: dict[str, Any], com: Command) -> ProjectSpec:
com.line_error("No poetry section found in the project spec")
return cls()
name = poetry.get("name", "root")
groups: dict[str, dict[str, list[ProjectDependency]]] = {}
groups: dict[str, dict[PackageName, list[ProjectDependency]]] = {}

def add_group(name: str, root: dict[str, Any]):
if name not in groups:
Expand All @@ -71,7 +72,7 @@ def add_group(name: str, root: dict[str, Any]):
else:
com.line_error(f"Invalid dependency specification: {raw_dep_spec!r}")
if dep_specs:
groups[name][package] = dep_specs
groups[name][to_package_name(package)] = dep_specs

add_group("main", poetry.get("dependencies", {}))
add_group("dev", poetry.get("dev-dependencies", {}))
Expand Down
10 changes: 10 additions & 0 deletions poetry_stale_dependencies/util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import re
from datetime import timedelta
from typing import NewType

PackageName = NewType("PackageName", str)
_package_repl_pattern = re.compile(r"[._-]+")


def to_package_name(name: str) -> PackageName:
name = _package_repl_pattern.sub("-", name).lower()
return PackageName(name)


def render_timedelta(td: timedelta) -> str:
Expand Down
27 changes: 16 additions & 11 deletions tests/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
from tests.util import simple_v1_package, simple_v1_release


@fixture(params=["foo", "foo-bar"])
def package_name(request):
return request.param


@fixture()
def foo_package(client):
client.sources["https://pypi.org/simple"]["foo"] = simple_v1_package(
def foo_package(client, package_name):
client.sources["https://pypi.org/simple"][package_name] = simple_v1_package(
{
"0.1.0": [
simple_v1_release("foo-0.1.0-py3-none-any.whl", "2020-01-01T00:00:00Z"),
Expand All @@ -38,15 +43,15 @@ def foo_package(client):


@fixture()
def project():
def project(package_name):
return ProjectSpec(
dependencies_groups={
"main": {
"foo": [ProjectDependency("yankee do", "is a teapot")],
package_name: [ProjectDependency("yankee do", "is a teapot")],
"bar": [ProjectDependency("something", "else")],
},
"dev": {
"foo": [ProjectDependency("yankee dev", "is a dev teapot")],
package_name: [ProjectDependency("yankee dev", "is a dev teapot")],
},
"docs": {"baz": [ProjectDependency("baz", "is a baz")]},
}
Expand All @@ -55,9 +60,9 @@ def project():

@mark.parametrize("ignore_post2", [True, False])
@mark.parametrize("ignore_prereleases", [True, False])
def test_inspect_stale(foo_package, client, source, ignore_post2, ignore_prereleases, project):
def test_inspect_stale(foo_package, client, source, ignore_post2, ignore_prereleases, project, package_name):
inspect_specs = PackageInspectSpecs(
"foo",
package_name,
source=source,
time_to_stale=timedelta(days=2),
time_to_ripe=timedelta(),
Expand All @@ -69,7 +74,7 @@ def test_inspect_stale(foo_package, client, source, ignore_post2, ignore_prerele
inspect_specs.ignore_versions.append("1.0.0post2")
assert inspect_specs.inspect_is_stale(client, MagicMock(), project, MagicMock()) == [
StalePackageInspectResults(
"foo",
package_name,
LegacyPackageSource.Pypi,
ResultsVersionSpec("1.0.0", date(2021, 1, 1)),
ResultsVersionSpec("1.0.1", date(2022, 1, 1)),
Expand All @@ -83,9 +88,9 @@ def test_inspect_stale(foo_package, client, source, ignore_post2, ignore_prerele


@mark.parametrize("ignore_post1", [True, False])
def test_inspect_not_stale(foo_package, client, source, ignore_post1):
def test_inspect_not_stale(foo_package, client, source, ignore_post1, package_name):
inspect_specs = PackageInspectSpecs(
"foo",
package_name,
source=source,
time_to_stale=timedelta(days=2),
time_to_ripe=timedelta(),
Expand All @@ -97,7 +102,7 @@ def test_inspect_not_stale(foo_package, client, source, ignore_post1):
inspect_specs.ignore_versions.append("1.0.0post")
assert inspect_specs.inspect_is_stale(client, MagicMock(), MagicMock(), MagicMock()) == [
NonStalePackageInspectResults(
"foo",
package_name,
LegacyPackageSource.Pypi,
ResultsVersionSpec("1.0.0", date(2021, 1, 1)),
ResultsVersionSpec("1.0.0", date(2021, 1, 1))
Expand Down
6 changes: 3 additions & 3 deletions tests/test_lockspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_parse_lock(lock_version):
"source": {"type": "legacy", "url": "https://pypi2.org/simple", "reference": "pypi2"},
},
{
"name": "booz",
"name": "Blue--booz",
"version": "2.1.0",
"source": {"type": "new", "url": "https://pypi2.org/simple", "reference": "pypi2"},
},
Expand Down Expand Up @@ -67,7 +67,7 @@ def test_parse_lock(lock_version):
PackageSpec("5.6.7", None, {}),
],
"baz": [PackageSpec("2.0.0", LegacyPackageSource("https://pypi2.org/simple", "pypi2"), {})],
"booz": [PackageSpec("2.1.0", None, {})],
"blue-booz": [PackageSpec("2.1.0", None, {})],
}

assert list(lock.get_packages(["foo", "baz"], MagicMock())) == [
Expand All @@ -77,5 +77,5 @@ def test_parse_lock(lock_version):
assert list(lock.get_packages(None, MagicMock())) == [
("foo", lock.packages["foo"]),
("baz", lock.packages["baz"]),
("booz", lock.packages["booz"]),
("blue-booz", lock.packages["blue-booz"]),
]
4 changes: 2 additions & 2 deletions tests/test_projspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_parse_projspec(name):
},
},
"dev-dependencies": {
"baz": "^3.0",
"blue_Baz": "^3.0",
},
"group": {
"doc": {
Expand Down Expand Up @@ -59,7 +59,7 @@ def test_parse_projspec(name):
"bar": [ProjectDependency("^2.0", "sys_platform == 'win32'")],
},
"dev": {
"baz": [ProjectDependency("^3.0", None)],
"blue-baz": [ProjectDependency("^3.0", None)],
"jim": [ProjectDependency("^7.0", None)],
},
"doc": {
Expand Down

0 comments on commit 760eabb

Please sign in to comment.