diff options
author | Benedikt Morbach <bmorbach@redhat.com> | 2014-08-11 13:23:46 +0200 |
---|---|---|
committer | Benedikt Morbach <bmorbach@redhat.com> | 2014-08-11 13:23:46 +0200 |
commit | 0ea795ea56787d22e12aa8db01c5424f529cf2f7 (patch) | |
tree | b254a0e7434964eb2ac592991ef5310ba9cd9631 | |
parent | fea0163338f56cfe372b72b044f296bd781b2060 (diff) | |
download | wheel-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.py | 33 | ||||
-rw-r--r-- | wheel/util.py | 21 |
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 |