Skip to content

Commit

Permalink
Merge pull request #625 from enthought/normalize_name_in_solver
Browse files Browse the repository at this point in the history
ENH: normalize package name and requirement name.
  • Loading branch information
cournape committed Dec 4, 2015
2 parents 034165d + 8b7fefe commit 6b3fa19
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 11 deletions.
2 changes: 1 addition & 1 deletion enstaller/new_solver/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def add_repository(self, repository):

self._id_to_package[current_id] = package
self._package_to_id[package] = current_id
self._packages_by_name[package._egg_name].append(package)
self._packages_by_name[package.name].append(package)

def what_provides(self, requirement):
""" Computes the list of packages fulfilling the given
Expand Down
5 changes: 2 additions & 3 deletions enstaller/new_solver/tests/test_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,15 @@ def test_what_provides_casing(self):
# Given
index_path = os.path.join(DATA_DIR, "numpy_index.json")
repository = repository_from_index(index_path)
requirement = Requirement._from_string("MKL ~= 10.2")
requirement = Requirement._from_string("mkl ~= 10.2")

# When
pool = Pool([repository])
candidates = pool.what_provides(requirement)
versions = [candidate.full_version for candidate in candidates]

# Then
assertCountEqual(self, versions,
["10.2-1", "10.2-2"])
assertCountEqual(self, versions, ["10.2-1", "10.2-2"])

def test_what_provides_simple(self):
# Given
Expand Down
4 changes: 0 additions & 4 deletions enstaller/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,6 @@ def version(self):
# ------------------
# Private properties
# ------------------
@property
def _egg_name(self):
return split_eggname(self.key)[0]

@property
def _comp_key(self):
return (self.name, self.version, self._dependencies, self.python_tag)
Expand Down
53 changes: 52 additions & 1 deletion enstaller/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys
import warnings

import six

from enstaller.collections import DefaultOrderedDict
from enstaller.eggcollect import info_from_metadir
from enstaller.errors import NoSuchPackage
Expand Down Expand Up @@ -324,13 +326,62 @@ def _version_factory(upstream, build):
cache[(upstream, build)] = version
return version

for key, info in json_dict.items():
requirement_normalizer = _RequirementNormalizer(json_dict)

for key, info in six.iteritems(json_dict):
info.setdefault('type', 'egg')
info.setdefault('packages', [])
info.setdefault('python', python_version)

version = _version_factory(info["version"], info["build"])
info = requirement_normalizer(key, info)

if python_version == "*" or info["python"] in (None, python_version):
yield RemotePackageMetadata.from_json_dict_and_version(
key, info, version, repository_info
)


class _RequirementNormalizer(object):
""" Normalize name/requirement names in our legacy index
For some reason, for each entry in our index, the name is always lower
case, but the requirement may be upper-case (e.g. MKL or Cython). To avoid
pushing this complexity down to the dependency solver, we normalize
requirement names to match names, e.g. is the package name is `mkl`, then a
requirement like `MKL 10.3` will be converted to `mkl 10.3`.
"""
def __init__(self, json_data):
egg_name_to_name = {}
for key, value in six.iteritems(json_data):
egg_name = key.split("-", 1)[0]
egg_name_to_name[egg_name] = value["name"]

self._egg_name_to_name = egg_name_to_name

def _normalizer(self, requirement_name):
return self._egg_name_to_name.get(requirement_name, requirement_name)

def _normalize_value(self, key, value):
egg_name = key.split("-", 1)[0]
if "packages" in value:
value["packages"] = _normalize_requirement_names(
value["packages"], self._normalizer
)
return value

def __call__(self, key, value):
return self._normalize_value(key, value)


def _normalize_requirement_names(requirements, normalizer):
def _normalize_requirement_name(requirement):
parts = requirement.split(None, 1)
if len(parts) > 0:
parts[0] = normalizer(parts[0])
return " ".join(parts)

return [
_normalize_requirement_name(requirement)
for requirement in requirements
]
2 changes: 1 addition & 1 deletion enstaller/tests/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def test_casing(self):
python)
# Then
self.assertEqual(metadata.name, "mkl")
self.assertEqual(metadata._egg_name, "MKL")
self.assertEqual(metadata.key, "MKL-10.3-1.egg")


class TestRepositoryPackage(unittest.TestCase):
Expand Down
61 changes: 60 additions & 1 deletion enstaller/tests/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from enstaller.versions import EnpkgVersion

from enstaller.package import PackageMetadata, RemotePackageMetadata
from enstaller.repository import Repository
from enstaller.repository import _RequirementNormalizer, Repository
from enstaller.repository_info import BroodRepositoryInfo, FSRepositoryInfo
from enstaller.solver import Requirement
from enstaller.tests.common import (SIMPLE_INDEX, WarningTestMixin,
Expand Down Expand Up @@ -494,3 +494,62 @@ def test_sorted_packages_invalid(self):
with self.assertWarns(DeprecationWarning):
deprecated_packages = repository.find_sorted_packages("numpy")
self.assertEqual(deprecated_packages, packages)


class Test_RequirementNormalizer(unittest.TestCase):
def test_simple(self):
# Given
r_normalized_numpy = {
"name": "numpy",
"packages": ["mkl 10.3-1"],
}

index = {
"MKL-10.3-1.egg": {
"name": "mkl",
"packages": [],
},
"numpy-1.9.2-1.egg": {
"name": "numpy",
"packages": ["MKL 10.3-1"],
},
}

# When
normalizer = _RequirementNormalizer(index)
normalized_numpy = normalizer(
"numpy-1.9.2-1.egg", index["numpy-1.9.2-1.egg"]
)

# Then
self.assertEqual(normalized_numpy, r_normalized_numpy)

def test_preserve_casing(self):
# AFAIK, name is always all lower-case, but we may want to support
# casing in names latter on (especially for interoperability w/ pypi)

# Given
r_normalized_dummy = {
"name": "dummy",
"packages": ["Cython 0.23.4"],
}

index = {
"Cython-0.23.4-1.egg": {
"name": "Cython",
"packages": [],
},
"dummy-1.0.0-1.egg": {
"name": "dummy",
"packages": ["Cython 0.23.4"],
},
}

# When
normalizer = _RequirementNormalizer(index)
normalized_dummy = normalizer(
"dummy-1.0.0-1.egg", index["dummy-1.0.0-1.egg"]
)

# Then
self.assertEqual(normalized_dummy, r_normalized_dummy)

0 comments on commit 6b3fa19

Please sign in to comment.