summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenedikt Morbach <bmorbach@redhat.com>2014-08-11 13:23:46 +0200
committerBenedikt Morbach <bmorbach@redhat.com>2014-08-11 13:23:46 +0200
commit0ea795ea56787d22e12aa8db01c5424f529cf2f7 (patch)
treeb254a0e7434964eb2ac592991ef5310ba9cd9631
parentfea0163338f56cfe372b72b044f296bd781b2060 (diff)
downloadwheel-0ea795ea56787d22e12aa8db01c5424f529cf2f7.tar.gz
make sure json metadata is sorted deterministically
python 2.6 doesn't have OrderedDict, so the entries might be random there. In practice, they should at least be stable per machine, as PYTHONHASHSEED == 0 in 2.6 The DefaultOrdereddict is from http://stackoverflow.com/questions/4126348/how-do-i-rewrite-this-function-to-implement-ordereddict/4127426#4127426 with the authors permission to use under MIT
-rw-r--r--wheel/metadata.py33
-rw-r--r--wheel/util.py21
2 files changed, 40 insertions, 14 deletions
diff --git a/wheel/metadata.py b/wheel/metadata.py
index b2318d4..40ce296 100644
--- a/wheel/metadata.py
+++ b/wheel/metadata.py
@@ -2,8 +2,13 @@
Tools for converting old- to new-style metadata.
"""
-from collections import defaultdict, namedtuple
+from collections import namedtuple
from .pkginfo import read_pkg_info
+from .util import OrderedDefaultDict
+try:
+ from collections import OrderedDict
+except ImportError:
+ OrderedDict = dict
import re
import os.path
@@ -51,8 +56,8 @@ def handle_requires(metadata, pkg_info, key):
"""
Place the runtime requirements from pkg_info into metadata.
"""
- may_requires = defaultdict(list)
- for value in pkg_info.get_all(key):
+ may_requires = OrderedDefaultDict(list)
+ for value in sorted(pkg_info.get_all(key)):
extra_match = EXTRA_RE.search(value)
if extra_match:
groupdict = extra_match.groupdict()
@@ -70,7 +75,7 @@ def handle_requires(metadata, pkg_info, key):
if may_requires:
metadata['run_requires'] = []
for key, value in may_requires.items():
- may_requirement = {'requires':value}
+ may_requirement = OrderedDict((('requires', value),))
if key.extra:
may_requirement['extra'] = key.extra
if key.condition:
@@ -93,7 +98,7 @@ def pkginfo_to_dict(path, distribution=None):
distribution: optional distutils Distribution()
"""
- metadata = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
+ metadata = OrderedDefaultDict(lambda: OrderedDefaultDict(lambda: OrderedDefaultDict(OrderedDict)))
metadata["generator"] = "bdist_wheel (" + wheel.__version__ + ")"
try:
unicode
@@ -121,7 +126,7 @@ def pkginfo_to_dict(path, distribution=None):
if description:
pkg_info['description'] = description
- for key in unique(k.lower() for k in pkg_info.keys()):
+ for key in sorted(unique(k.lower() for k in pkg_info.keys())):
low_key = key.replace('-', '_')
if low_key in SKIP_FIELDS:
@@ -130,7 +135,7 @@ def pkginfo_to_dict(path, distribution=None):
if low_key in UNKNOWN_FIELDS and pkg_info.get(key) == 'UNKNOWN':
continue
- if low_key in PLURAL_FIELDS:
+ if low_key in sorted(PLURAL_FIELDS):
metadata[PLURAL_FIELDS[low_key]] = pkg_info.get_all(key)
elif low_key == "requires_dist":
@@ -161,7 +166,7 @@ def pkginfo_to_dict(path, distribution=None):
try:
requirements = getattr(distribution, attr)
if isinstance(requirements, list):
- new_requirements = list(convert_requirements(requirements))
+ new_requirements = sorted(convert_requirements(requirements))
metadata[requires] = [{'requires':new_requirements}]
except AttributeError:
pass
@@ -169,8 +174,8 @@ def pkginfo_to_dict(path, distribution=None):
# handle contacts
contacts = []
for contact_type, role in CONTACT_FIELDS:
- contact = {}
- for key in contact_type:
+ contact = OrderedDict()
+ for key in sorted(contact_type):
if contact_type[key] in metadata:
contact[key] = metadata.pop(contact_type[key])
if contact:
@@ -183,10 +188,10 @@ def pkginfo_to_dict(path, distribution=None):
try:
with open(os.path.join(os.path.dirname(path), "entry_points.txt"), "r") as ep_file:
ep_map = pkg_resources.EntryPoint.parse_map(ep_file.read())
- exports = {}
- for group, items in ep_map.items():
- exports[group] = {}
- for item in items.values():
+ exports = OrderedDict()
+ for group, items in sorted(ep_map.items()):
+ exports[group] = OrderedDict()
+ for item in sorted(items.values()):
name, export = str(item).split(' = ', 1)
exports[group][name] = export
if exports:
diff --git a/wheel/util.py b/wheel/util.py
index bc2eb5e..5268813 100644
--- a/wheel/util.py
+++ b/wheel/util.py
@@ -5,6 +5,10 @@ import os
import base64
import json
import hashlib
+try:
+ from collections import OrderedDict
+except ImportError:
+ OrderedDict = dict
__all__ = ['urlsafe_b64encode', 'urlsafe_b64decode', 'utf8',
'to_json', 'from_json', 'matches_requirement']
@@ -91,6 +95,23 @@ class HashingFile(object):
digest = self.hash.digest()
return self.hashtype + '=' + native(urlsafe_b64encode(digest))
+class OrderedDefaultDict(OrderedDict):
+ def __init__(self, *args, **kwargs):
+ if not args:
+ self.default_factory = None
+ else:
+ if not (args[0] is None or callable(args[0])):
+ raise TypeError('first argument must be callable or None')
+ self.default_factory = args[0]
+ args = args[1:]
+ super(OrderedDefaultDict, self).__init__(*args, **kwargs)
+
+ def __missing__ (self, key):
+ if self.default_factory is None:
+ raise KeyError(key)
+ self[key] = default = self.default_factory()
+ return default
+
if sys.platform == 'win32':
import ctypes.wintypes
# CSIDL_APPDATA for reference - not used here for compatibility with