diff --git a/.travis.yml b/.travis.yml index b0d445c19..8c361b280 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,13 @@ sudo: false python: - "2.7.11" before_install: - - pip install -U pip==7 - pip install flake8 - flake8 remo - mysql -e 'create database remo character set utf8; SET @@GLOBAL.wait_timeout=28800;' install: - npm install -g less - pip install coverage - - bin/peep.py install -r requirements/dev.txt + - pip install --require-hashes --no-deps -r requirements/dev.txt script: - coverage run --source=remo manage.py test after_success: diff --git a/bin/peep.py b/bin/peep.py deleted file mode 100755 index 6b9393a5e..000000000 --- a/bin/peep.py +++ /dev/null @@ -1,959 +0,0 @@ -#!/usr/bin/env python -"""peep ("prudently examine every package") verifies that packages conform to a -trusted, locally stored hash and only then installs them:: - - peep install -r requirements.txt - -This makes your deployments verifiably repeatable without having to maintain a -local PyPI mirror or use a vendor lib. Just update the version numbers and -hashes in requirements.txt, and you're all set. - -""" -# This is here so embedded copies of peep.py are MIT-compliant: -# Copyright (c) 2013 Erik Rose -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -from __future__ import print_function -try: - xrange = xrange -except NameError: - xrange = range -from base64 import urlsafe_b64encode, urlsafe_b64decode -from binascii import hexlify -import cgi -from collections import defaultdict -from functools import wraps -from hashlib import sha256 -from itertools import chain, islice -import mimetypes -from optparse import OptionParser -from os.path import join, basename, splitext, isdir -from pickle import dumps, loads -import re -import sys -from shutil import rmtree, copy -from sys import argv, exit -from tempfile import mkdtemp -import traceback -try: - from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError -except ImportError: - from urllib.request import build_opener, HTTPHandler, HTTPSHandler - from urllib.error import HTTPError -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse # 3.4 -# TODO: Probably use six to make urllib stuff work across 2/3. - -from pkg_resources import require, VersionConflict, DistributionNotFound - -# We don't admit our dependency on pip in setup.py, lest a naive user simply -# say `pip install peep.tar.gz` and thus pull down an untrusted copy of pip -# from PyPI. Instead, we make sure it's installed and new enough here and spit -# out an error message if not: - - -def activate(specifier): - """Make a compatible version of pip importable. Raise a RuntimeError if we - couldn't.""" - try: - for distro in require(specifier): - distro.activate() - except (VersionConflict, DistributionNotFound): - raise RuntimeError('The installed version of pip is too old; peep ' - 'requires ' + specifier) - -# Before 0.6.2, the log module wasn't there, so some -# of our monkeypatching fails. It probably wouldn't be -# much work to support even earlier, though. -activate('pip>=0.6.2') - -import pip -from pip.commands.install import InstallCommand -try: - from pip.download import url_to_path # 1.5.6 -except ImportError: - try: - from pip.util import url_to_path # 0.7.0 - except ImportError: - from pip.util import url_to_filename as url_to_path # 0.6.2 -from pip.index import PackageFinder, Link -try: - from pip.log import logger -except ImportError: - from pip import logger # 6.0 -from pip.req import parse_requirements -try: - from pip.utils.ui import DownloadProgressBar, DownloadProgressSpinner -except ImportError: - class NullProgressBar(object): - def __init__(self, *args, **kwargs): - pass - - def iter(self, ret, *args, **kwargs): - return ret - - DownloadProgressBar = DownloadProgressSpinner = NullProgressBar - -__version__ = 2, 5, 0 - -try: - from pip.index import FormatControl # noqa - FORMAT_CONTROL_ARG = 'format_control' - - # The line-numbering bug will be fixed in pip 8. All 7.x releases had it. - PIP_MAJOR_VERSION = int(pip.__version__.split('.')[0]) - PIP_COUNTS_COMMENTS = PIP_MAJOR_VERSION >= 8 -except ImportError: - FORMAT_CONTROL_ARG = 'use_wheel' # pre-7 - PIP_COUNTS_COMMENTS = True - - -ITS_FINE_ITS_FINE = 0 -SOMETHING_WENT_WRONG = 1 -# "Traditional" for command-line errors according to optparse docs: -COMMAND_LINE_ERROR = 2 - -ARCHIVE_EXTENSIONS = ('.tar.bz2', '.tar.gz', '.tgz', '.tar', '.zip') - -MARKER = object() - - -class PipException(Exception): - """When I delegated to pip, it exited with an error.""" - - def __init__(self, error_code): - self.error_code = error_code - - -class UnsupportedRequirementError(Exception): - """An unsupported line was encountered in a requirements file.""" - - -class DownloadError(Exception): - def __init__(self, link, exc): - self.link = link - self.reason = str(exc) - - def __str__(self): - return 'Downloading %s failed: %s' % (self.link, self.reason) - - -def encoded_hash(sha): - """Return a short, 7-bit-safe representation of a hash. - - If you pass a sha256, this results in the hash algorithm that the Wheel - format (PEP 427) uses, except here it's intended to be run across the - downloaded archive before unpacking. - - """ - return urlsafe_b64encode(sha.digest()).decode('ascii').rstrip('=') - - -def path_and_line(req): - """Return the path and line number of the file from which an - InstallRequirement came. - - """ - path, line = (re.match(r'-r (.*) \(line (\d+)\)$', - req.comes_from).groups()) - return path, int(line) - - -def hashes_above(path, line_number): - """Yield hashes from contiguous comment lines before line ``line_number``. - - """ - def hash_lists(path): - """Yield lists of hashes appearing between non-comment lines. - - The lists will be in order of appearance and, for each non-empty - list, their place in the results will coincide with that of the - line number of the corresponding result from `parse_requirements` - (which changed in pip 7.0 to not count comments). - - """ - hashes = [] - with open(path) as file: - for lineno, line in enumerate(file, 1): - match = HASH_COMMENT_RE.match(line) - if match: # Accumulate this hash. - hashes.append(match.groupdict()['hash']) - if not IGNORED_LINE_RE.match(line): - yield hashes # Report hashes seen so far. - hashes = [] - elif PIP_COUNTS_COMMENTS: - # Comment: count as normal req but have no hashes. - yield [] - - return next(islice(hash_lists(path), line_number - 1, None)) - - -def run_pip(initial_args): - """Delegate to pip the given args (starting with the subcommand), and raise - ``PipException`` if something goes wrong.""" - status_code = pip.main(initial_args) - - # Clear out the registrations in the pip "logger" singleton. Otherwise, - # loggers keep getting appended to it with every run. Pip assumes only one - # command invocation will happen per interpreter lifetime. - logger.consumers = [] - - if status_code: - raise PipException(status_code) - - -def hash_of_file(path): - """Return the hash of a downloaded file.""" - with open(path, 'rb') as archive: - sha = sha256() - while True: - data = archive.read(2 ** 20) - if not data: - break - sha.update(data) - return encoded_hash(sha) - - -def is_git_sha(text): - """Return whether this is probably a git sha""" - # Handle both the full sha as well as the 7-character abbreviation - if len(text) in (40, 7): - try: - int(text, 16) - return True - except ValueError: - pass - return False - - -def filename_from_url(url): - parsed = urlparse(url) - path = parsed.path - return path.split('/')[-1] - - -def requirement_args(argv, want_paths=False, want_other=False): - """Return an iterable of filtered arguments. - - :arg argv: Arguments, starting after the subcommand - :arg want_paths: If True, the returned iterable includes the paths to any - requirements files following a ``-r`` or ``--requirement`` option. - :arg want_other: If True, the returned iterable includes the args that are - not a requirement-file path or a ``-r`` or ``--requirement`` flag. - - """ - was_r = False - for arg in argv: - # Allow for requirements files named "-r", don't freak out if there's a - # trailing "-r", etc. - if was_r: - if want_paths: - yield arg - was_r = False - elif arg in ['-r', '--requirement']: - was_r = True - else: - if want_other: - yield arg - -# any line that is a comment or just whitespace -IGNORED_LINE_RE = re.compile(r'^(\s*#.*)?\s*$') - -HASH_COMMENT_RE = re.compile( - r""" - \s*\#\s+ # Lines that start with a '#' - (?Psha256):\s+ # Hash type is hardcoded to be sha256 for now. - (?P[^\s]+) # Hashes can be anything except '#' or spaces. - \s* # Suck up whitespace before the comment or - # just trailing whitespace if there is no - # comment. Also strip trailing newlines. - (?:\#(?P.*))? # Comments can be anything after a whitespace+# - # and are optional. - $""", re.X) - - -def peep_hash(argv): - """Return the peep hash of one or more files, returning a shell status code - or raising a PipException. - - :arg argv: The commandline args, starting after the subcommand - - """ - parser = OptionParser( - usage='usage: %prog hash file [file ...]', - description='Print a peep hash line for one or more files: for ' - 'example, "# sha256: ' - 'oz42dZy6Gowxw8AelDtO4gRgTW_xPdooH484k7I5EOY".') - _, paths = parser.parse_args(args=argv) - if paths: - for path in paths: - print('# sha256:', hash_of_file(path)) - return ITS_FINE_ITS_FINE - else: - parser.print_usage() - return COMMAND_LINE_ERROR - - -class EmptyOptions(object): - """Fake optparse options for compatibility with pip<1.2 - - pip<1.2 had a bug in parse_requirements() in which the ``options`` kwarg - was required. We work around that by passing it a mock object. - - """ - default_vcs = None - skip_requirements_regex = None - isolated_mode = False - - -def memoize(func): - """Memoize a method that should return the same result every time on a - given instance. - - """ - @wraps(func) - def memoizer(self): - if not hasattr(self, '_cache'): - self._cache = {} - if func.__name__ not in self._cache: - self._cache[func.__name__] = func(self) - return self._cache[func.__name__] - return memoizer - - -def package_finder(argv): - """Return a PackageFinder respecting command-line options. - - :arg argv: Everything after the subcommand - - """ - # We instantiate an InstallCommand and then use some of its private - # machinery--its arg parser--for our own purposes, like a virus. This - # approach is portable across many pip versions, where more fine-grained - # ones are not. Ignoring options that don't exist on the parser (for - # instance, --use-wheel) gives us a straightforward method of backward - # compatibility. - try: - command = InstallCommand() - except TypeError: - # This is likely pip 1.3.0's "__init__() takes exactly 2 arguments (1 - # given)" error. In that version, InstallCommand takes a top=level - # parser passed in from outside. - from pip.baseparser import create_main_parser - command = InstallCommand(create_main_parser()) - # The downside is that it essentially ruins the InstallCommand class for - # further use. Calling out to pip.main() within the same interpreter, for - # example, would result in arguments parsed this time turning up there. - # Thus, we deepcopy the arg parser so we don't trash its singletons. Of - # course, deepcopy doesn't work on these objects, because they contain - # uncopyable regex patterns, so we pickle and unpickle instead. Fun! - options, _ = loads(dumps(command.parser)).parse_args(argv) - - # Carry over PackageFinder kwargs that have [about] the same names as - # options attr names: - possible_options = [ - 'find_links', FORMAT_CONTROL_ARG, 'allow_external', 'allow_unverified', - 'allow_all_external', ('allow_all_prereleases', 'pre'), - 'process_dependency_links'] - kwargs = {} - for option in possible_options: - kw, attr = option if isinstance(option, tuple) else (option, option) - value = getattr(options, attr, MARKER) - if value is not MARKER: - kwargs[kw] = value - - # Figure out index_urls: - index_urls = [options.index_url] + options.extra_index_urls - if options.no_index: - index_urls = [] - index_urls += getattr(options, 'mirrors', []) - - # If pip is new enough to have a PipSession, initialize one, since - # PackageFinder requires it: - if hasattr(command, '_build_session'): - kwargs['session'] = command._build_session(options) - - return PackageFinder(index_urls=index_urls, **kwargs) - - -class DownloadedReq(object): - """A wrapper around InstallRequirement which offers additional information - based on downloading and examining a corresponding package archive - - These are conceptually immutable, so we can get away with memoizing - expensive things. - - """ - def __init__(self, req, argv, finder): - """Download a requirement, compare its hashes, and return a subclass - of DownloadedReq depending on its state. - - :arg req: The InstallRequirement I am based on - :arg argv: The args, starting after the subcommand - - """ - self._req = req - self._argv = argv - self._finder = finder - - # We use a separate temp dir for each requirement so requirements - # (from different indices) that happen to have the same archive names - # don't overwrite each other, leading to a security hole in which the - # latter is a hash mismatch, the former has already passed the - # comparison, and the latter gets installed. - self._temp_path = mkdtemp(prefix='peep-') - # Think of DownloadedReq as a one-shot state machine. It's an abstract - # class that ratchets forward to being one of its own subclasses, - # depending on its package status. Then it doesn't move again. - self.__class__ = self._class() - - def dispose(self): - """Delete temp files and dirs I've made. Render myself useless. - - Do not call further methods on me after calling dispose(). - - """ - rmtree(self._temp_path) - - def _version(self): - """Deduce the version number of the downloaded package from its filename.""" - # TODO: Can we delete this method and just print the line from the - # reqs file verbatim instead? - def version_of_archive(filename, package_name): - # Since we know the project_name, we can strip that off the left, strip - # any archive extensions off the right, and take the rest as the - # version. - for ext in ARCHIVE_EXTENSIONS: - if filename.endswith(ext): - filename = filename[:-len(ext)] - break - # Handle github sha tarball downloads. - if is_git_sha(filename): - filename = package_name + '-' + filename - if not filename.lower().replace('_', '-').startswith(package_name.lower()): - # TODO: Should we replace runs of [^a-zA-Z0-9.], not just _, with -? - give_up(filename, package_name) - return filename[len(package_name) + 1:] # Strip off '-' before version. - - def version_of_wheel(filename, package_name): - # For Wheel files (http://legacy.python.org/dev/peps/pep-0427/#file- - # name-convention) we know the format bits are '-' separated. - whl_package_name, version, _rest = filename.split('-', 2) - # Do the alteration to package_name from PEP 427: - our_package_name = re.sub(r'[^\w\d.]+', '_', package_name, re.UNICODE) - if whl_package_name != our_package_name: - give_up(filename, whl_package_name) - return version - - def give_up(filename, package_name): - raise RuntimeError("The archive '%s' didn't start with the package name " - "'%s', so I couldn't figure out the version number. " - "My bad; improve me." % - (filename, package_name)) - - get_version = (version_of_wheel - if self._downloaded_filename().endswith('.whl') - else version_of_archive) - return get_version(self._downloaded_filename(), self._project_name()) - - def _is_always_unsatisfied(self): - """Returns whether this requirement is always unsatisfied - - This would happen in cases where we can't determine the version - from the filename. - - """ - # If this is a github sha tarball, then it is always unsatisfied - # because the url has a commit sha in it and not the version - # number. - url = self._url() - if url: - filename = filename_from_url(url) - if filename.endswith(ARCHIVE_EXTENSIONS): - filename, ext = splitext(filename) - if is_git_sha(filename): - return True - return False - - @memoize # Avoid hitting the file[cache] over and over. - def _expected_hashes(self): - """Return a list of known-good hashes for this package.""" - return hashes_above(*path_and_line(self._req)) - - def _download(self, link): - """Download a file, and return its name within my temp dir. - - This does no verification of HTTPS certs, but our checking hashes - makes that largely unimportant. It would be nice to be able to use the - requests lib, which can verify certs, but it is guaranteed to be - available only in pip >= 1.5. - - This also drops support for proxies and basic auth, though those could - be added back in. - - """ - # Based on pip 1.4.1's URLOpener but with cert verification removed - def opener(is_https): - if is_https: - opener = build_opener(HTTPSHandler()) - # Strip out HTTPHandler to prevent MITM spoof: - for handler in opener.handlers: - if isinstance(handler, HTTPHandler): - opener.handlers.remove(handler) - else: - opener = build_opener() - return opener - - # Descended from unpack_http_url() in pip 1.4.1 - def best_filename(link, response): - """Return the most informative possible filename for a download, - ideally with a proper extension. - - """ - content_type = response.info().get('content-type', '') - filename = link.filename # fallback - # Have a look at the Content-Disposition header for a better guess: - content_disposition = response.info().get('content-disposition') - if content_disposition: - type, params = cgi.parse_header(content_disposition) - # We use ``or`` here because we don't want to use an "empty" value - # from the filename param: - filename = params.get('filename') or filename - ext = splitext(filename)[1] - if not ext: - ext = mimetypes.guess_extension(content_type) - if ext: - filename += ext - if not ext and link.url != response.geturl(): - ext = splitext(response.geturl())[1] - if ext: - filename += ext - return filename - - # Descended from _download_url() in pip 1.4.1 - def pipe_to_file(response, path, size=0): - """Pull the data off an HTTP response, shove it in a new file, and - show progress. - - :arg response: A file-like object to read from - :arg path: The path of the new file - :arg size: The expected size, in bytes, of the download. 0 for - unknown or to suppress progress indication (as for cached - downloads) - - """ - def response_chunks(chunk_size): - while True: - chunk = response.read(chunk_size) - if not chunk: - break - yield chunk - - print('Downloading %s%s...' % ( - self._req.req, - (' (%sK)' % (size / 1000)) if size > 1000 else '')) - progress_indicator = (DownloadProgressBar(max=size).iter if size - else DownloadProgressSpinner().iter) - with open(path, 'wb') as file: - for chunk in progress_indicator(response_chunks(4096), 4096): - file.write(chunk) - - url = link.url.split('#', 1)[0] - try: - response = opener(urlparse(url).scheme != 'http').open(url) - except (HTTPError, IOError) as exc: - raise DownloadError(link, exc) - filename = best_filename(link, response) - try: - size = int(response.headers['content-length']) - except (ValueError, KeyError, TypeError): - size = 0 - pipe_to_file(response, join(self._temp_path, filename), size=size) - return filename - - # Based on req_set.prepare_files() in pip bb2a8428d4aebc8d313d05d590f386fa3f0bbd0f - @memoize # Avoid re-downloading. - def _downloaded_filename(self): - """Download the package's archive if necessary, and return its - filename. - - --no-deps is implied, as we have reimplemented the bits that would - ordinarily do dependency resolution. - - """ - # Peep doesn't support requirements that don't come down as a single - # file, because it can't hash them. Thus, it doesn't support editable - # requirements, because pip itself doesn't support editable - # requirements except for "local projects or a VCS url". Nor does it - # support VCS requirements yet, because we haven't yet come up with a - # portable, deterministic way to hash them. In summary, all we support - # is == requirements and tarballs/zips/etc. - - # TODO: Stop on reqs that are editable or aren't ==. - - # If the requirement isn't already specified as a URL, get a URL - # from an index: - link = self._link() or self._finder.find_requirement(self._req, upgrade=False) - - if link: - lower_scheme = link.scheme.lower() # pip lower()s it for some reason. - if lower_scheme == 'http' or lower_scheme == 'https': - file_path = self._download(link) - return basename(file_path) - elif lower_scheme == 'file': - # The following is inspired by pip's unpack_file_url(): - link_path = url_to_path(link.url_without_fragment) - if isdir(link_path): - raise UnsupportedRequirementError( - "%s: %s is a directory. So that it can compute " - "a hash, peep supports only filesystem paths which " - "point to files" % - (self._req, link.url_without_fragment)) - else: - copy(link_path, self._temp_path) - return basename(link_path) - else: - raise UnsupportedRequirementError( - "%s: The download link, %s, would not result in a file " - "that can be hashed. Peep supports only == requirements, " - "file:// URLs pointing to files (not folders), and " - "http:// and https:// URLs pointing to tarballs, zips, " - "etc." % (self._req, link.url)) - else: - raise UnsupportedRequirementError( - "%s: couldn't determine where to download this requirement from." - % (self._req,)) - - def install(self): - """Install the package I represent, without dependencies. - - Obey typical pip-install options passed in on the command line. - - """ - other_args = list(requirement_args(self._argv, want_other=True)) - archive_path = join(self._temp_path, self._downloaded_filename()) - # -U so it installs whether pip deems the requirement "satisfied" or - # not. This is necessary for GitHub-sourced zips, which change without - # their version numbers changing. - run_pip(['install'] + other_args + ['--no-deps', '-U', archive_path]) - - @memoize - def _actual_hash(self): - """Download the package's archive if necessary, and return its hash.""" - return hash_of_file(join(self._temp_path, self._downloaded_filename())) - - def _project_name(self): - """Return the inner Requirement's "unsafe name". - - Raise ValueError if there is no name. - - """ - name = getattr(self._req.req, 'project_name', '') - if name: - return name - raise ValueError('Requirement has no project_name.') - - def _name(self): - return self._req.name - - def _link(self): - try: - return self._req.link - except AttributeError: - # The link attribute isn't available prior to pip 6.1.0, so fall - # back to the now deprecated 'url' attribute. - return Link(self._req.url) if self._req.url else None - - def _url(self): - link = self._link() - return link.url if link else None - - @memoize # Avoid re-running expensive check_if_exists(). - def _is_satisfied(self): - self._req.check_if_exists() - return (self._req.satisfied_by and - not self._is_always_unsatisfied()) - - def _class(self): - """Return the class I should be, spanning a continuum of goodness.""" - try: - self._project_name() - except ValueError: - return MalformedReq - if self._is_satisfied(): - return SatisfiedReq - if not self._expected_hashes(): - return MissingReq - if self._actual_hash() not in self._expected_hashes(): - return MismatchedReq - return InstallableReq - - @classmethod - def foot(cls): - """Return the text to be printed once, after all of the errors from - classes of my type are printed. - - """ - return '' - - -class MalformedReq(DownloadedReq): - """A requirement whose package name could not be determined""" - - @classmethod - def head(cls): - return 'The following requirements could not be processed:\n' - - def error(self): - return '* Unable to determine package name from URL %s; add #egg=' % self._url() - - -class MissingReq(DownloadedReq): - """A requirement for which no hashes were specified in the requirements file""" - - @classmethod - def head(cls): - return ('The following packages had no hashes specified in the requirements file, which\n' - 'leaves them open to tampering. Vet these packages to your satisfaction, then\n' - 'add these "sha256" lines like so:\n\n') - - def error(self): - if self._url(): - # _url() always contains an #egg= part, or this would be a - # MalformedRequest. - line = self._url() - else: - line = '%s==%s' % (self._name(), self._version()) - return '# sha256: %s\n%s\n' % (self._actual_hash(), line) - - -class MismatchedReq(DownloadedReq): - """A requirement for which the downloaded file didn't match any of my hashes.""" - @classmethod - def head(cls): - return ("THE FOLLOWING PACKAGES DIDN'T MATCH THE HASHES SPECIFIED IN THE REQUIREMENTS\n" - "FILE. If you have updated the package versions, update the hashes. If not,\n" - "freak out, because someone has tampered with the packages.\n\n") - - def error(self): - preamble = ' %s: expected' % self._project_name() - if len(self._expected_hashes()) > 1: - preamble += ' one of' - padding = '\n' + ' ' * (len(preamble) + 1) - return '%s %s\n%s got %s' % (preamble, - padding.join(self._expected_hashes()), - ' ' * (len(preamble) - 4), - self._actual_hash()) - - @classmethod - def foot(cls): - return '\n' - - -class SatisfiedReq(DownloadedReq): - """A requirement which turned out to be already installed""" - - @classmethod - def head(cls): - return ("These packages were already installed, so we didn't need to download or build\n" - "them again. If you installed them with peep in the first place, you should be\n" - "safe. If not, uninstall them, then re-attempt your install with peep.\n") - - def error(self): - return ' %s' % (self._req,) - - -class InstallableReq(DownloadedReq): - """A requirement whose hash matched and can be safely installed""" - - -# DownloadedReq subclasses that indicate an error that should keep us from -# going forward with installation, in the order in which their errors should -# be reported: -ERROR_CLASSES = [MismatchedReq, MissingReq, MalformedReq] - - -def bucket(things, key): - """Return a map of key -> list of things.""" - ret = defaultdict(list) - for thing in things: - ret[key(thing)].append(thing) - return ret - - -def first_every_last(iterable, first, every, last): - """Execute something before the first item of iter, something else for each - item, and a third thing after the last. - - If there are no items in the iterable, don't execute anything. - - """ - did_first = False - for item in iterable: - if not did_first: - did_first = True - first(item) - every(item) - if did_first: - last(item) - - -def _parse_requirements(path, finder): - try: - # list() so the generator that is parse_requirements() actually runs - # far enough to report a TypeError - return list(parse_requirements( - path, options=EmptyOptions(), finder=finder)) - except TypeError: - # session is a required kwarg as of pip 6.0 and will raise - # a TypeError if missing. It needs to be a PipSession instance, - # but in older versions we can't import it from pip.download - # (nor do we need it at all) so we only import it in this except block - from pip.download import PipSession - return list(parse_requirements( - path, options=EmptyOptions(), session=PipSession(), finder=finder)) - - -def downloaded_reqs_from_path(path, argv): - """Return a list of DownloadedReqs representing the requirements parsed - out of a given requirements file. - - :arg path: The path to the requirements file - :arg argv: The commandline args, starting after the subcommand - - """ - finder = package_finder(argv) - return [DownloadedReq(req, argv, finder) for req in - _parse_requirements(path, finder)] - - -def peep_install(argv): - """Perform the ``peep install`` subcommand, returning a shell status code - or raising a PipException. - - :arg argv: The commandline args, starting after the subcommand - - """ - output = [] - out = output.append - reqs = [] - try: - req_paths = list(requirement_args(argv, want_paths=True)) - if not req_paths: - out("You have to specify one or more requirements files with the -r option, because\n" - "otherwise there's nowhere for peep to look up the hashes.\n") - return COMMAND_LINE_ERROR - - # We're a "peep install" command, and we have some requirement paths. - reqs = list(chain.from_iterable( - downloaded_reqs_from_path(path, argv) - for path in req_paths)) - buckets = bucket(reqs, lambda r: r.__class__) - - # Skip a line after pip's "Cleaning up..." so the important stuff - # stands out: - if any(buckets[b] for b in ERROR_CLASSES): - out('\n') - - printers = (lambda r: out(r.head()), - lambda r: out(r.error() + '\n'), - lambda r: out(r.foot())) - for c in ERROR_CLASSES: - first_every_last(buckets[c], *printers) - - if any(buckets[b] for b in ERROR_CLASSES): - out('-------------------------------\n' - 'Not proceeding to installation.\n') - return SOMETHING_WENT_WRONG - else: - for req in buckets[InstallableReq]: - req.install() - - first_every_last(buckets[SatisfiedReq], *printers) - - return ITS_FINE_ITS_FINE - except (UnsupportedRequirementError, DownloadError) as exc: - out(str(exc)) - return SOMETHING_WENT_WRONG - finally: - for req in reqs: - req.dispose() - print(''.join(output)) - - -def peep_port(paths): - """Convert a peep requirements file to one compatble with pip-8 hashing. - - Loses comments and tromps on URLs, so the result will need a little manual - massaging, but the hard part--the hash conversion--is done for you. - - """ - if not paths: - print('Please specify one or more requirements files so I have ' - 'something to port.\n') - return COMMAND_LINE_ERROR - for req in chain.from_iterable( - _parse_requirements(path, package_finder(argv)) for path in paths): - hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii') - for hash in hashes_above(*path_and_line(req))] - if not hashes: - print(req.req) - elif len(hashes) == 1: - print('%s --hash=sha256:%s' % (req.req, hashes[0])) - else: - print('%s' % req.req, end='') - for hash in hashes: - print(' \\') - print(' --hash=sha256:%s' % hash, end='') - print() - - -def main(): - """Be the top-level entrypoint. Return a shell status code.""" - commands = {'hash': peep_hash, - 'install': peep_install, - 'port': peep_port} - try: - if len(argv) >= 2 and argv[1] in commands: - return commands[argv[1]](argv[2:]) - else: - # Fall through to top-level pip main() for everything else: - return pip.main() - except PipException as exc: - return exc.error_code - - -def exception_handler(exc_type, exc_value, exc_tb): - print('Oh no! Peep had a problem while trying to do stuff. Please write up a bug report') - print('with the specifics so we can fix it:') - print() - print('https://github.com/erikrose/peep/issues/new') - print() - print('Here are some particulars you can copy and paste into the bug report:') - print() - print('---') - print('peep:', repr(__version__)) - print('python:', repr(sys.version)) - print('pip:', repr(getattr(pip, '__version__', 'no __version__ attr'))) - print('Command line: ', repr(sys.argv)) - print( - ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))) - print('---') - - -if __name__ == '__main__': - try: - exit(main()) - except Exception: - exception_handler(*sys.exc_info()) - exit(SOMETHING_WENT_WRONG) diff --git a/bin/pipstrap.py b/bin/pipstrap.py new file mode 100755 index 000000000..505f8ca72 --- /dev/null +++ b/bin/pipstrap.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +"""A small script that can act as a trust root for installing pip 8 + +Embed this in your project, and your VCS checkout is all you have to trust. In +a post-peep era, this lets you claw your way to a hash-checking version of pip, +with which you can install the rest of your dependencies safely. All it assumes +is Python 2.6 or better and *some* version of pip already installed. If +anything goes wrong, it will exit with a non-zero status code. + +""" +# This is here so embedded copies are MIT-compliant: +# Copyright (c) 2016 Erik Rose +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +from __future__ import print_function +from hashlib import sha256 +from os.path import join +from pipes import quote +from shutil import rmtree +try: + from subprocess import check_output +except ImportError: + from subprocess import CalledProcessError, PIPE, Popen + + def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be ' + 'overridden.') + process = Popen(stdout=PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd) + return output +from sys import exit, version_info +from tempfile import mkdtemp +try: + from urllib2 import build_opener, HTTPHandler, HTTPSHandler +except ImportError: + from urllib.request import build_opener, HTTPHandler, HTTPSHandler +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse # 3.4 + + +__version__ = 1, 1, 1 + + +# wheel has a conditional dependency on argparse: +maybe_argparse = ( + [('https://pypi.python.org/packages/source/a/argparse/' + 'argparse-1.4.0.tar.gz', + '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] + if version_info < (2, 7, 0) else []) + + +PACKAGES = maybe_argparse + [ + # Pip has no dependencies, as it vendors everything: + ('https://pypi.python.org/packages/source/p/pip/pip-8.0.3.tar.gz', + '30f98b66f3fe1069c529a491597d34a1c224a68640c82caf2ade5f88aa1405e8'), + # This version of setuptools has only optional dependencies: + ('https://pypi.python.org/packages/source/s/setuptools/' + 'setuptools-20.2.2.tar.gz', + '24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'), + ('https://pypi.python.org/packages/source/w/wheel/wheel-0.29.0.tar.gz', + '1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648') +] + + +class HashError(Exception): + def __str__(self): + url, path, actual, expected = self.args + return ('{url} did not match the expected hash {expected}. Instead, ' + 'it was {actual}. The file (left at {path}) may have been ' + 'tampered with.'.format(**locals())) + + +def hashed_download(url, temp, digest): + """Download ``url`` to ``temp``, make sure it has the SHA-256 ``digest``, + and return its path.""" + # Based on pip 1.4.1's URLOpener but with cert verification removed. Python + # >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert + # authenticity has only privacy (not arbitrary code execution) + # implications, since we're checking hashes. + def opener(): + opener = build_opener(HTTPSHandler()) + # Strip out HTTPHandler to prevent MITM spoof: + for handler in opener.handlers: + if isinstance(handler, HTTPHandler): + opener.handlers.remove(handler) + return opener + + def read_chunks(response, chunk_size): + while True: + chunk = response.read(chunk_size) + if not chunk: + break + yield chunk + + response = opener().open(url) + path = join(temp, urlparse(url).path.split('/')[-1]) + actual_hash = sha256() + with open(path, 'wb') as file: + for chunk in read_chunks(response, 4096): + file.write(chunk) + actual_hash.update(chunk) + + actual_digest = actual_hash.hexdigest() + if actual_digest != digest: + raise HashError(url, path, actual_digest, digest) + return path + + +def main(): + temp = mkdtemp(prefix='pipstrap-') + try: + downloads = [hashed_download(url, temp, digest) + for url, digest in PACKAGES] + check_output('pip install --no-index --no-deps -U ' + + ' '.join(quote(d) for d in downloads), + shell=True) + except HashError as exc: + print(exc) + except Exception: + rmtree(temp) + raise + else: + rmtree(temp) + return 0 + return 1 + + +if __name__ == '__main__': + exit(main()) diff --git a/bin/update/update.py b/bin/update/update.py index a5cbbee1b..54321481b 100644 --- a/bin/update/update.py +++ b/bin/update/update.py @@ -107,8 +107,9 @@ def setup_dependencies(ctx): activate_env = os.path.join(venv_bin_path, 'activate_this.py') execfile(activate_env, dict(__file__=activate_env)) + ctx.local('python ./bin/pipstrap.py') ctx.local('pip --version') - ctx.local('./peep.sh install -r requirements/prod.txt') + ctx.local('pip install --require-hashes --no-deps -r requirements/prod.txt') # Make the venv relocatable ctx.local('virtualenv-2.7 --relocatable venv') diff --git a/peep.sh b/peep.sh deleted file mode 100755 index 4e1373445..000000000 --- a/peep.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -set -eu - -# Get pip version number -PIPVER=`pip --version | awk '{print $2}'` -ARGS="" - -echo "peep.sh: Using pip $PIPVER" - -# Add pip arguments that vary according to the version of pip, here: -case $PIPVER in - 1.5*|6.*) - # Pip uses the wheel format packages by default in pip 1.5+. - # However in pip < 6.0+ wheel support is broken, and even with pip 6.0+ - # we intentionally don't use the wheel packages, since otherwise each - # package in the requirements files would need multiple hashes. - echo "peep.sh: Wheel-using pip detected, so passing --no-use-wheel." - ARGS="$ARGS --no-use-wheel" - ;; - 7.*) - echo "peep.sh: Binary-using pip detected, so passing --no-binary=:all:" - ARGS="$ARGS --no-binary=:all:" - ;; - 1.*) - # A version of pip that won't automatically use wheels. - ;; - *) - echo "peep.sh: Unrecognized version of pip: $PIPVER". - echo "peep.sh: I don't know what to do about that." - echo "peep.sh: Maybe try to install pip 7?" - echo "peep.sh: \"pip install -U 'pip==7'\"" - exit 1 - ;; -esac - -# Add the version specific arguments to those passed on the command line. -python ./bin/peep.py "$@" $ARGS - -echo "peep.sh: Done!" diff --git a/requirements/dev.txt b/requirements/dev.txt index 7425fa64b..37be90934 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3,158 +3,159 @@ # package for developers (testing, docs, etc.), it goes in this file. -r prod.txt -# sha256: XOPHHFVFtHLaF7ciaJeJFNAlKYA0hjaEC9NKALXMlsE -# sha256: sVi233bt0jm4II1IHcRrav1FqEa3gS_wzliXHPW8i7o -mock==2.0.0 -# sha256: NFOvRsW-7vPnUD0eEG4MxuuLMHsJb34Xsb26q2T9noE -# sha256: xocaeE0kq6knC2soVBU3pX4vz018eZQQ66GCNrx21rw -Sphinx==1.4.1 -# sha256: _5spJogkCW_JETkEYF2D9qqBcQLA8XARW0s58K4GUfM -# sha256: dPYz7Tph2h0dWcMYVIPIGp1zRr8Oe18prQdkpvFZtoo -sphinx_rtd_theme==0.1.8 -# sha256: Egr4uJMGHscr5KqRuTRV5tDUqAF1wHpTgZkQxNiXSDA -# sha256: CMxlW-wlusPmYKstHFFHHUUy7zk_xA-vvSUBQCY1Frs -sphinx-autobuild==0.5.2 -# sha256: R5lTTzXrocB8tvmFmqW7cZiGdp9dNdKjjnSQzpDAzmk -# sha256: -Y-ZHSsDkDOsX6pjjA0a-ycgq_TZ14FXPDWS1omUgKE -django-extensions==1.6.1 -# sha256: uAicJCv1vQlorw9lti08uEgu6rW7wTZYxZQKRl7qgUU -# sha256: qNx8JGds7mf5S5BrsszdznInqM4Mgkg0JBm5Qh33t9g -# sha256: TSIF8QvjDsVzIUpoDoKRxALAjCSjJVzcw3rhZ46JLnM -# sha256: eh_4lLjBZnTFpqpicnzgpj-CI-TwOgtXHa-8GUHa3qY -# sha256: -lwpVw5ECvV6EtElNtcJcpFJw0qnISXVted466HS_10 -# sha256: r0uhrGKCcrrI4omu-bxB5l-VTkbCUA1dDkd5KGltTw4 -# sha256: nNKYc3oL0pETG3740-FLd88ryLX-AOKmpnJPQxHZH48 -# sha256: 8G-uJGkBNYZaOWHqjMR7fWOqPxHmvRedjcF7R5njeAs -# sha256: 5bURLQ-hYkae6WVnlEFZiha3qKp9mXBUw42K3nZZuD0 -# sha256: Rj4FX1jT7Tl1ADQzQeJCIlKNXTRZ1qV6TTVYjT5DvW0 -# sha256: Wwdasm-LgANpeDfi7xXkztIDCtY2cKHZxomHQDNBWzY -# sha256: u-YZvMIr-yoIeDtFu7W4PNngmwfuxWXCLeJIG2UT2jg -# sha256: W7GWxtrIhI6GejXtXoLcjQy-mga7h8kIAvjY4BR3Gzg -# sha256: jKykgGZ0M0GBHSPq7iogUpFv7WYyebFgeG6l2yLLCXM -# sha256: dA0c_a_pXXjLiTccw74enSeHYZe6Ed0W0W1Ahrg-aPo -# sha256: buCHjmrNI52QJL1BfqhSHArz3j_lvNcDwwxSHcmUUdc -# sha256: I09JUjyUsOWIqdLwr1XseoWkwwLpF_dT7_KkRLxA0E0 -# sha256: s7zt_VZ9aMIQjhDIduGtZw_BIXwPAFB0ihZGcmMOSX8 -# sha256: 1zk2I9z1zHzmfj8nyVp6UlmcHa5DgiZkBYmKt7OGvMk -# sha256: QXcrpiLBRnxnT-FHMLgdr8OlCmcHqIwX3_icykTqLHA -# sha256: WbNQaCnShECpKt2AxLMDW3_yehRxTzodOyQT5JuMpwc -# sha256: i7gCsdw8r08dtHD0_R9vZhe5eYaeqL-V8ZM1QxwzPkc -# sha256: F5pNCBKVMQkfQYXhRV3sjeSlG6JpmoY04hu0bn3fX_c -# sha256: jWWlfAeIcJXKjebu_2xYbkpsQBQp899n8EjyTU1p02I -# sha256: 6UNZsBmbTv9vSccxZLQOpa4K2PQOQImFHsBI_G93BJA -# sha256: rdL4kqGBDSyFJLMDFRA0ozkejYPGPsDvwHEb22TCsI8 -# sha256: ZCHrv99buuROW-W3Qd6wDwCnARvUUnMaJglfST0fVbs -# sha256: 7DhUnoZKprr0wJHI0QABO8SOKpWztauJBZYCej1dcgQ -coverage==4.1b2 -# sha256: -1pnr0AkYiKHp2q_a3_k-zz6z3ZaeQl2zmT1LETIjko -# sha256: zB5YF59s8QUkx7_dN49VNtCmFJdohRd5Fjml7MhnSS8 -flake8==2.5.4 -# sha256: 3OvUkoESYxYm9MTQ31l4fHSEBOZt2pUhEAMOqIPTuM0 -# sha256: x9txeBCraWX2bIzwOYqYydjfmC2jm0zX8WKRHriVlvo -docutils==0.12 -# sha256: BIA1TyWy9EPk7xRWtI8orRyqptMW_KWl6qnKd0WueSM -# sha256: mEUq9kUOKMnHQtVn116246ezka1M6KvVZ5xfhc5_rQA -# sha256: 26QvGCtfbyZjDSIC79MDg3Etn32NjZiWs3riFF3sphY -# sha256: 2FL-1Z2mfH5FyyGSAn2ov9kgp4VtKVwkekUQWWjSTVo -ipython==4.2.0 -# sha256: WbOakYplSuytAXpvFnYAd5B1KkXWTDUDBQf0UnmkaeM -ipdb==0.10.0 -# sha256: T8LkeK3c8XAWZX3_MLLY1hHoNB-sGczydogC9mNde4o -# sha256: oRPV9a16errO-d9ew_KvI6IKKABZIVd7Fd1YTQmdWQA -pep8==1.7.0 -# sha256: 74ZW8DS5rdF6lbPJebJoYm08KRb1AuZqrHJp9ohyuN4 -# sha256: coSkDe5JOThL6oIJ2Lt10g8pFCqPeCE3NHelbME7Yvk -# sha256: j9_uo_GFS_0Re_Ch8T4Uwzd78UwbcZH0l_No3wjcu1U -# sha256: RWbo2A8TreNuVaxgybuaNUARWmMUr1aS2gnQGhV5Vyg -# sha256: 3wGVheAIbhNALniqQmlz24WypaKGu313z2NUpoJMQI4 -# sha256: nHTKKKfwww3KiHIoGzxHcF4hIXyLxjkS2VyeKnysa98 -# sha256: 39rk4YWIeXKGiDMy7vT1qjsy4uhCGTuEg4jcTzahEaM -# sha256: vqm88EZ6uHQb-vMdqx_RFJhDEvKxHary_4YQ-tiGnns -# sha256: S0tHt8Kr4b9zsDewgbR35ZQe5pkPihyoYaxnJ4TRl_Y -# sha256: OHnQ3i1hYEGiWnaw05iY-ruyzpXTDTuruJIL43h1T-o -# sha256: XE7yAfP9zeWRceDUn_LyHChQvsZpHqtpU6JeH1a9D1c -lxml==3.6.0 -# sha256: LIkzG9lST--6nncalN6M1GaPtzSfJ7-hu-VzI90GvBQ -# sha256: -8lc9CKsefoAxRB6LzPf990QbW3laUk72TiIG3XULkk -pyquery==1.2.13 -# sha256: BTWn4nAUh0snrjpNM-h0njRb36YnZhlSCLeZa_EQBoI -cssselect==0.9.1 -# sha256: kfiCvhjW0gPT9qyKi3TBjXl0qRBE6mN9Z1m-sylaRZ8 -# sha256: 16HUYiIQA38tHPxbZcCoei-wTMWMCMP7r36rx6HcSPs -pbr==1.9.0 -# sha256: HJFt-7Sq0lDypA6Tfc_yBtoWX6KfqQnuHqAiQ_c4YBk -# sha256: IxD51Kd8KE6SDsVy3CUlNmoQewjSFv-Nu4kdlbandWM -funcsigs==1.0.0 -# sha256: HDW0rCBs7y0kgWyJ-Jzyid09OM98RJuz-re_bUPwGx8 -ordereddict==1.1 -# sha256: nzvNPEAcPoYuwOvm0sBp68ASzhQsziCcCYzLWwkTbok -# sha256: kZ8mpossF6djTamT2RM54oiWT5PCdPE0Pju74gluESg -snowballstemmer==1.2.1 -# sha256: 1XYCs9cwwuy5eKIT-s4LehbOqkomNXU2G9T9niZppUQ -# sha256: 9BaoTg0N28KI9rjywnbRC0DKEjhWLNntWnUSkuxke3E -alabaster==0.7.7 -# sha256: 3JcuBglLmvW4VbPfSmRjleQ9HJ0NOe00W3OTVg0LkXM -simplegeneric==0.8.1 -# sha256: w2yTiocuX_SUk4szsUqqFWy0OexnVI_Ks1Nbt4sIRug -# sha256: tUtHgtpgx9_l2FBSRBbdA_O5pRbtR2zh1aJxLN511oM -# sha256: LbPUxU0-tHrXFpgG5oTVJD1KFHScuXQe9dGsMpHWeho -# sha256: PowQSGG1g5o25rMML5ZNb79KLiuHrpwonaaZsh41yRs -# sha256: egLSIgnkG6Vb2KZfpp7lk68wqFf24z_JyAWJ5rR3w2s -# sha256: 2bcoFdPNxeHD8fREmKXohP5NzbP2Qo-ghuNa-BYNKAk -# sha256: yBsMzPa2v8BDLBqWwHtkt2RyEY7HibxIkUR8xSmL0cc -# sha256: 47wlKMOg85aQi1-HhHlfP3ti6LJXPC23Nq3cz7IkSeQ -# sha256: yhKWYycRdPl4PhF3GV5CiJRaUE24n1w4idp14Ku9Cmc -# sha256: 1Evm53gC6oRZEeAA4p14G6KQCg_js4_A9bdPX3fS5PA -# sha256: Krvgsje0LgdeWln5B2bAoYzCmuo7qnoVLMFvYvpVbao -# sha256: rVWonMJkt0vlnfn9zqHf_RT0NdlV6uKsgiooVjvi_kg -# sha256: 4lwgdBietyBWd4yINE9H5ec3iv1S2mIBS7PobvzpR8E -# sha256: Gbs6w1Dvh43ahKYtN8fVwXoTc4bd6cLOcknHoh1_ask -PyYAML==3.11 -# sha256: BqdELLkTD7iAb-M2AA_PIO3x8vitIF57Ys7BGFBVENs -argh==0.26.1 -# sha256: dcM9VG4KcypGBnScutzYGSnzDYuBQGHKk83kmTPbuGA -# sha256: Xo7M-VkkZYyXuZC1BVKt22T1Xh49_kiARWrB8ofcedA -certifi==2016.2.28 -# sha256: gztGlmaHs95_Q4x2GsR1IT5TswZ0Dxq_qobh0arlaqg -# sha256: Wwavh98TgY0U8IoCjkL1ZmQK74CAXDtQxQVrCG48K5w -singledispatch==3.4.0.3 -# sha256: BCJVFguxl20qo9ng8yrH-MG8Lfq3sshmiKO9YIi6kOE -# sha256: KV4fOCJc4r3YWgUk4mXprepAUHMzgp86fWTFiN14_yE -# sha256: ycLTJZPRbu3yzsG2pBiTYmomSbQLIcqcTKxCQ73i778 -tornado==4.3 -# sha256: fDXFQho5u4LlgBj-vZDjtuXbNMVEOqr3QrPzPUZV8cA -pathtools==0.1.2 -# sha256: fmWIKtt3RgObbzh27hdJUvjqqjRJG6NDM93x_jXeQWI -watchdog==0.8.3 -# sha256: uhmrrZEkmk8rvi-AMQk5gptJBAlq266H6Mqq7dkF5t8 -# sha256: iHzJl21y12FvpXyCxO9b9don4jUN_W9l0_ROhu_FG5I -livereload==2.4.1 -# sha256: y8KTj2wBBhvG0h0Mg4wkiWZHVcsYZ28HNNdhf0V30J4 -# sha256: misS69h253xy5B6_QBzC58W1ZmSdUBBcpJgiaIZCIHs -mccabe==0.4.0 -# sha256: ouKi9o6DIYIstozPaHpNjBZo6FATUmdlnOgVLYat8Ng -# sha256: NjPgAP_cMH_xp9dFDolf-IE-ILCE7yY7Vmnu-bxMelI -pyflakes==1.2.0 -# sha256: 9HGFUjJsmVRKbsYC2Wt9A-9hGAz0pJLFFeyyQ43RTMw -# sha256: kAIugzFjY3iKVTUv45z77TV6o6cdkOXygDo1Rx3ku6g -decorator==4.0.9 -# sha256: BaZoQ8lqMg7sCd9nTBb_MwpDywf3Mc8r2IqjZFoYBUE -# sha256: duujPIlyO4_AJPlQysr1vy7zeZlkLMmmH058HKXPCsA -# sha256: 1tsyATlfm5VXhtJaGBfAcpHivLlut_QWg644NoNhedc -traitlets==4.2.1 -# sha256: JOCzPhMztV5zydHpqDQkF9UZ93ianTtED0rNAOpFFX4 -# sha256: 3rOpYMHVWGjfvKyYQyNYuSuonZUCnN3UBA2x8nQFBVw -pathlib2==2.1.0 -# sha256: tYz31wZYoJFiHA2Mw1FDyFafOCdJayftiWkYwjfQXZY -# sha256: ku47DiFjJULsyaCiReaaEm9i5RFAgb2w0y4O3RBBADM -pickleshare==0.7.2 -# sha256: Rky3b3pxInQ90lUHZQ24nNRHxR845GcWArPqouOOBa4 -# sha256: BTDOY6kpW_rnvQbtwCtqqTVhn0hvDx3Aly9RYmXugaY -ptyprocess==0.5.1 -# sha256: IyeV68qvLhIDltu6o6Ep7aUXV-6q4ZEVWPTvjuQU_Gw -pexpect==4.0.1 -# sha256: Yhjpq9YS-1rPsXXqfHsCYAbeTflpHZpzybOQz6GkHCs -# sha256: OgYkolGiZGPJ36D_pjXsUcQmU4CYDZpQ1lYRw8K9gqY -# sha256: DEP6hOk60OTb7K_8ZlasHK8aSDWbK7Cl2jr4QWTj9Js -ipython_genutils==0.1.0 + +mock==2.0.0 \ + --hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \ + --hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba +Sphinx==1.4.1 \ + --hash=sha256:3453af46c5beeef3e7503d1e106e0cc6eb8b307b096f7e17b1bdbaab64fd9e81 \ + --hash=sha256:c6871a784d24aba9270b6b28541537a57e2fcf4d7c799410eba18236bc76d6bc +sphinx_rtd_theme==0.1.8 \ + --hash=sha256:ff9b29268824096fc9113904605d83f6aa817102c0f170115b4b39f0ae0651f3 \ + --hash=sha256:74f633ed3a61da1d1d59c3185483c81a9d7346bf0e7b5f29ad0764a6f159b68a +sphinx-autobuild==0.5.2 \ + --hash=sha256:120af8b893061ec72be4aa91b93455e6d0d4a80175c07a53819910c4d8974830 \ + --hash=sha256:08cc655bec25bac3e660ab2d1c51471d4532ef393fc40fafbd250140263516bb +django-extensions==1.6.1 \ + --hash=sha256:4799534f35eba1c07cb6f9859aa5bb719886769f5d35d2a38e7490ce90c0ce69 \ + --hash=sha256:f98f991d2b039033ac5faa638c0d1afb2720abf4d9d781573c3592d6899480a1 +coverage==4.1b2 \ + --hash=sha256:b8089c242bf5bd0968af0f65b62d3cb8482eeab5bbc13658c5940a465eea8145 \ + --hash=sha256:a8dc7c24676cee67f94b906bb2ccddce7227a8ce0c8248342419b9421df7b7d8 \ + --hash=sha256:4d2205f10be30ec573214a680e8291c402c08c24a3255cdcc37ae1678e892e73 \ + --hash=sha256:7a1ff894b8c16674c5a6aa62727ce0a63f8223e4f03a0b571dafbc1941dadea6 \ + --hash=sha256:fa5c29570e440af57a12d12536d709729149c34aa72125d5b5e778eba1d2ff5d \ + --hash=sha256:af4ba1ac628272bac8e289aef9bc41e65f954e46c2500d5d0e477928696d4f0e \ + --hash=sha256:9cd298737a0bd291131b7ef8d3e14b77cf2bc8b5fe00e2a6a6724f4311d91f8f \ + --hash=sha256:f06fae24690135865a3961ea8cc47b7d63aa3f11e6bd179d8dc17b4799e3780b \ + --hash=sha256:e5b5112d0fa162469ee965679441598a16b7a8aa7d997054c38d8ade7659b83d \ + --hash=sha256:463e055f58d3ed397500343341e24222528d5d3459d6a57a4d35588d3e43bd6d \ + --hash=sha256:5b075ab26f8b8003697837e2ef15e4ced2030ad63670a1d9c689874033415b36 \ + --hash=sha256:bbe619bcc22bfb2a08783b45bbb5b83cd9e09b07eec565c22de2481b6513da38 \ + --hash=sha256:5bb196c6dac8848e867a35ed5e82dc8d0cbe9a06bb87c90802f8d8e014771b38 \ + --hash=sha256:8caca48066743341811d23eaee2a2052916fed663279b160786ea5db22cb0973 \ + --hash=sha256:740d1cfdafe95d78cb89371cc3be1e9d27876197ba11dd16d16d4086b83e68fa \ + --hash=sha256:6ee0878e6acd239d9024bd417ea8521c0af3de3fe5bcd703c30c521dc99451d7 \ + --hash=sha256:234f49523c94b0e588a9d2f0af55ec7a85a4c302e917f753eff2a444bc40d04d \ + --hash=sha256:b3bcedfd567d68c2108e10c876e1ad670fc1217c0f0050748a164672630e497f \ + --hash=sha256:d7393623dcf5cc7ce67e3f27c95a7a52599c1dae4382266405898ab7b386bcc9 \ + --hash=sha256:41772ba622c1467c674fe14730b81dafc3a50a6707a88c17dff89cca44ea2c70 \ + --hash=sha256:59b3506829d28440a92add80c4b3035b7ff27a14714f3a1d3b2413e49b8ca707 \ + --hash=sha256:8bb802b1dc3caf4f1db470f4fd1f6f6617b979869ea8bf95f19335431c333e47 \ + --hash=sha256:179a4d08129531091f4185e1455dec8de4a51ba2699a8634e21bb46e7ddf5ff7 \ + --hash=sha256:8d65a57c07887095ca8de6eeff6c586e4a6c401429f3df67f048f24d4d69d362 \ + --hash=sha256:e94359b0199b4eff6f49c73164b40ea5ae0ad8f40e4089851ec048fc6f770490 \ + --hash=sha256:add2f892a1810d2c8524b303151034a3391e8d83c63ec0efc0711bdb64c2b08f \ + --hash=sha256:6421ebbfdf5bbae44e5be5b741deb00f00a7011bd452731a26095f493d1f55bb \ + --hash=sha256:ec38549e864aa6baf4c091c8d100013bc48e2a95b3b5ab890596027a3d5d7204 +flake8==2.5.4 \ + --hash=sha256:fb5a67af4024622287a76abf6b7fe4fb3cfacf765a790976ce64f52c44c88e4a \ + --hash=sha256:cc1e58179f6cf10524c7bfdd378f5536d0a61497688517791639a5ecc867492f +docutils==0.12 \ + --hash=sha256:dcebd4928112631626f4c4d0df59787c748404e66dda952110030ea883d3b8cd \ + --hash=sha256:c7db717810ab6965f66c8cf0398a98c9d8df982da39b4cd7f162911eb89596fa +ipython==4.2.0 \ + --hash=sha256:0480354f25b2f443e4ef1456b48f28ad1caaa6d316fca5a5eaa9ca7745ae7923 \ + --hash=sha256:98452af6450e28c9c742d567d75eb6e3a7b391ad4ce8abd5679c5f85ce7fad00 \ + --hash=sha256:dba42f182b5f6f26630d2202efd30383712d9f7d8d8d9896b37ae2145deca616 \ + --hash=sha256:d852fed59da67c7e45cb2192027da8bfd920a7856d295c247a45105968d24d5a +ipdb==0.10.0 \ + --hash=sha256:59b39a918a654aecad017a6f1676007790752a45d64c35030507f45279a469e3 +pep8==1.7.0 \ + --hash=sha256:4fc2e478addcf17016657dff30b2d8d611e8341fac19ccf2768802f6635d7b8a \ + --hash=sha256:a113d5f5ad7a7abacef9df5ec3f2af23a20a28005921577b15dd584d099d5900 +lxml==3.6.0 \ + --hash=sha256:ef8656f034b9add17a95b3c979b268626d3c2916f502e66aac7269f68872b8de \ + --hash=sha256:7284a40dee4939384bea8209d8bb75d20f29142a8f7821373477a56cc13b62f9 \ + --hash=sha256:8fdfeea3f1854bfd117bf0a1f13e14c3377bf14c1b7191f497f368df08dcbb55 \ + --hash=sha256:4566e8d80f13ade36e55ac60c9bb9a3540115a6314af5692da09d01a15795728 \ + --hash=sha256:df019585e0086e13402e78aa426973db85b2a5a286bb7d77cf6354a6824c408e \ + --hash=sha256:9c74ca28a7f0c30dca8872281b3c47705e21217c8bc63912d95c9e2a7cac6bdf \ + --hash=sha256:dfdae4e18588797286883332eef4f5aa3b32e2e842193b848388dc4f36a111a3 \ + --hash=sha256:bea9bcf0467ab8741bfaf31dab1fd114984312f2b11daaf2ff8610fad8869e7b \ + --hash=sha256:4b4b47b7c2abe1bf73b037b081b477e5941ee6990f8a1ca861ac672784d197f6 \ + --hash=sha256:3879d0de2d616041a25a76b0d39898fabbb2ce95d30d3babb8920be378754fea \ + --hash=sha256:5c4ef201f3fdcde59171e0d49ff2f21c2850bec6691eab6953a25e1f56bd0f57 +pyquery==1.2.13 \ + --hash=sha256:2c89331bd9524fefba9e771a94de8cd4668fb7349f27bfa1bbe57323dd06bc14 \ + --hash=sha256:fbc95cf422ac79fa00c5107a2f33dff7dd106d6de569493bd938881b75d42e49 +cssselect==0.9.1 \ + --hash=sha256:0535a7e27014874b27ae3a4d33e8749e345bdfa62766195208b7996bf1100682 +pbr==1.9.0 \ + --hash=sha256:91f882be18d6d203d3f6ac8a8b74c18d7974a91044ea637d6759beb3295a459f \ + --hash=sha256:d7a1d4622210037f2d1cfc5b65c0a87a2fb04cc58c08c3fbaf7eabc7a1dc48fb +funcsigs==1.0.0 \ + --hash=sha256:1c916dfbb4aad250f2a40e937dcff206da165fa29fa909ee1ea02243f7386019 \ + --hash=sha256:2310f9d4a77c284e920ec572dc2525366a107b08d216ff8dbb891d95b6a77563 +ordereddict==1.1 \ + --hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f +snowballstemmer==1.2.1 \ + --hash=sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89 \ + --hash=sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128 +alabaster==0.7.7 \ + --hash=sha256:d57602b3d730c2ecb978a213face0b7a16ceaa4a263575361bd4fd9e2669a544 \ + --hash=sha256:f416a84e0d0ddbc288f6b8f2c276d10b40ca1238562cd9ed5a751292ec647b71 +simplegeneric==0.8.1 \ + --hash=sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173 +PyYAML==3.11 \ + --hash=sha256:c36c938a872e5ff494938b33b14aaa156cb439ec67548fcab3535bb78b0846e8 \ + --hash=sha256:b54b4782da60c7dfe5d850524416dd03f3b9a516ed476ce1d5a2712cde75d683 \ + --hash=sha256:2db3d4c54d3eb47ad7169806e684d5243d4a14749cb9741ef5d1ac3291d67a1a \ + --hash=sha256:3e8c104861b5839a36e6b30c2f964d6fbf4a2e2b87ae9c289da699b21e35c91b \ + --hash=sha256:7a02d22209e41ba55bd8a65fa69ee593af30a857f6e33fc9c80589e6b477c36b \ + --hash=sha256:d9b72815d3cdc5e1c3f1f44498a5e884fe4dcdb3f6428fa086e35af8160d2809 \ + --hash=sha256:c81b0cccf6b6bfc0432c1a96c07b64b76472118ec789bc4891447cc5298bd1c7 \ + --hash=sha256:e3bc2528c3a0f396908b5f8784795f3f7b62e8b2573c2db736addccfb22449e4 \ + --hash=sha256:ca129663271174f9783e1177195e4288945a504db89f5c3889da75e0abbd0a67 \ + --hash=sha256:d44be6e77802ea845911e000e29d781ba2900a0fe3b38fc0f5b74f5f77d2e4f0 \ + --hash=sha256:2abbe0b237b42e075e5a59f90766c0a18cc29aea3baa7a152cc16f62fa556daa \ + --hash=sha256:ad55a89cc264b74be59df9fdcea1dffd14f435d955eae2ac822a28563be2fe48 \ + --hash=sha256:e25c2074189eb72056778c88344f47e5e7378afd52da62014bb3e86efce947c1 \ + --hash=sha256:19bb3ac350ef878dda84a62d37c7d5c17a137386dde9c2ce7249c7a21d7f6ac9 +argh==0.26.1 \ + --hash=sha256:06a7442cb9130fb8806fe336000fcf20edf1f2f8ad205e7b62cec118505510db +certifi==2016.2.28 \ + --hash=sha256:75c33d546e0a732a4606749cbadcd81929f30d8b814061ca93cde49933dbb860 \ + --hash=sha256:5e8eccf95924658c97b990b50552addb64f55e1e3dfe4880456ac1f287dc79d0 +singledispatch==3.4.0.3 \ + --hash=sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8 \ + --hash=sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c +tornado==4.3 \ + --hash=sha256:042255160bb1976d2aa3d9e0f32ac7f8c1bc2dfab7b2c86688a3bd6088ba90e1 \ + --hash=sha256:295e1f38225ce2bdd85a0524e265e9adea40507333829f3a7d64c588dd78ff21 \ + --hash=sha256:c9c2d32593d16eedf2cec1b6a41893626a2649b40b21ca9c4cac4243bde2efbf +pathtools==0.1.2 \ + --hash=sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0 +watchdog==0.8.3 \ + --hash=sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162 +livereload==2.4.1 \ + --hash=sha256:ba19abad91249a4f2bbe2f80310939829b4904096adbae87e8caaaedd905e6df \ + --hash=sha256:887cc9976d72d7616fa57c82c4ef5bf5da27e2350dfd6f65d3f44e86efc51b92 +mccabe==0.4.0 \ + --hash=sha256:cbc2938f6c01061bc6d21d0c838c2489664755cb18676f0734d7617f4577d09e \ + --hash=sha256:9a2b12ebd876e77c72e41ebf401cc2e7c5b566649d50105ca49822688642207b +pyflakes==1.2.0 \ + --hash=sha256:a2e2a2f68e8321822cb68ccf687a4d8c1668e850135267659ce8152d86adf0d8 \ + --hash=sha256:3633e000ffdc307ff1a7d7450e895ff8813e20b084ef263b5669eef9bc4c7a52 +decorator==4.0.9 \ + --hash=sha256:f4718552326c99544a6ec602d96b7d03ef61180cf4a492c515ecb2438dd14ccc \ + --hash=sha256:90022e83316363788a55352fe39cfbed357aa3a71d90e5f2803a35471de4bba8 +traitlets==4.2.1 \ + --hash=sha256:05a66843c96a320eec09df674c16ff330a43cb07f731cf2bd88aa3645a180541 \ + --hash=sha256:76eba33c89723b8fc024f950cacaf5bf2ef37999642cc9a61f4e7c1ca5cf0ac0 \ + --hash=sha256:d6db3201395f9b955786d25a1817c07291e2bcb96eb7f41683ae3836836179d7 +pathlib2==2.1.0 \ + --hash=sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e \ + --hash=sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c +pickleshare==0.7.2 \ + --hash=sha256:b58cf7d70658a091621c0d8cc35143c8569f3827496b27ed896918c237d05d96 \ + --hash=sha256:92ee3b0e21632542ecc9a0a245e69a126f62e5114081bdb0d32e0edd10410033 +ptyprocess==0.5.1 \ + --hash=sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae \ + --hash=sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6 +pexpect==4.0.1 \ + --hash=sha256:232795ebcaaf2e120396dbbaa3a129eda51757eeaae1911558f4ef8ee414fc6c +ipython_genutils==0.1.0 \ + --hash=sha256:6218e9abd612fb5acfb175ea7c7b026006de4df9691d9a73c9b390cfa1a41c2b \ + --hash=sha256:3a0624a251a26463c9dfa0ffa635ec51c4265380980d9a50d65611c3c2bd82a6 \ + --hash=sha256:0c43fa84e93ad0e4dbecaffc6656ac1caf1a48359b2bb0a5da3af84164e3f49b diff --git a/requirements/prod.txt b/requirements/prod.txt index a6886b2d9..d9b3b0a1d 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,252 +1,222 @@ # Adding peep so we can track it on requires.io. For packages # installation we use peep.py shipped in ./bin -# sha256: KoBM4H9Zz1WtVFuy4WMSwRNkuU0_k4bW4SFFsuOOXBw -peep==2.4.1 -# sha256: c1CauJkYk_5byimUihn9wtv5xN1KDpiqnFgDvveKsiY -# sha256: zpqSSG7aW_PZ5Ah_gCSSsVtxsGuJTWgM-Wb1wnlD5TI -# sha256: m7S7kSEJqteivOjiKGp9mgnrzKrv5Br4VYZrZgcVkGA -peepin==0.11 -# sha256: 2tXaDNfzzKfaOsQqGau6MPXMEPrkl25HQFHnCFtOldE -# sha256: Eo6L3BHGnqkPd4Q104EmRT078oPb0ozxWjOqjlIkXfQ -Django==1.8.13 -# sha256: HMA-8ytkvhngpbVFeN15CQajSUP-kQLP2uDUSVvVNrQ -# sha256: vB_y_4jb-s795N3eRx0UF9OzBOjfEDp6lDfUcmkgG_Q -Jinja2==2.8 - -# sha256: pOwa_1m5WhS0XrLiN2GgF56YMZ2lp-t2tW6ozce4ccM -MarkupSafe==0.23 - -# sha256: 0yV2uq13_dnmhZvpfCtqPBem9IQ2Q4nbcx8Gl7ehlmE -django-csp==2.0.3 - -# sha256: mWbSw_hrIzdNqYkFhPFrA_bkYFWCTjChCHFpdNRdHkU -# sha256: 98W-TbhvGnbMxMgH-QCFNidavsPigV0YJl1pSu7zyzw -# sha256: bOkY5I2u1knhOqTrLOmdgCEkEUAb5CKIyURzqBEJJE4 -# sha256: ZEHnmPiGN0TJBXTeJTvtMoxHf9LIbK9SuNnYXx7Ln5Y -# sha256: TWTtG54OcwlfXPqH8Ol920yEAEno7-t-Y7RhGLodYjo -# sha256: Oe8FQeSJsc9VFc7fGx-Z3lhf1mBGPNzeQ6A9fZIEJ4I -# sha256: aFsZlAJlchd0WK8UPMJKIIL0APGOY_3I4u1o3ZO7D0k -# sha256: hEYBPfvemz9Pm_6glBjd1SZ2CoeYxBut0_cHqYrQdFs -# sha256: 1f9wYInpFQdg0ji2YgOiTUoKYXUWBBAZGZX_BNGJP6o -# sha256: p4tITVRy3Yxoj4s-7hhkaiXGbORbLCZlKFD2r5zlKxc -# sha256: xO5wy0B_koRResNo8SHPB5anE0uWHlPZ2vGqro9E-5A -# sha256: APNfsOfi_DSM0JHSXTbGdotyFGPRI5bWipSRqKX5Hs8 -pytz==2015.4 - -# sha256: WbUVu327zdMH-BesOIVOgIOev6M8XbeX9lRQeTyUXA0 -newrelic==2.52.0.40 - -# sha256: CjoiZeHvsN76ci0fQpcw3J35da3DxgrH0rQyvfA8BB0 -# sha256: Xe0vqQlP19_rPakmNkCf1wKg0H1gYoNQTX7gRAHO5cs -# sha256: cyCRkITm2sj0VAY4pGRHo71zD8oXKvwX0sA-7SLPT1E -Pygments==2.0.2 -# sha256: _CtqZSAbJ3EegA6y4SYv9Coy8Ce9ImfjQSHxvSvWuTM -django-jinja==2.1.2 - -# sha256: qyLRMiCZCYcwpX_VnWEPYHOPlaHLaNrMotHEfLDL6O4 -# sha256: gRBAtkfl1WhvhNtBXv1pfmJQAIsRK2kJunesBZ4UDHQ -MySQL-python==1.2.5 - -# sha256: sGqGRRqZ1sJr-mUIKZnb9ljgh-WW6SkszmiLU-q6qto -django-sha2==0.4 -# sha256: xhLLJ-qGcXbEJKQwWeHgWPF8Pu0rY1wIhBPlUhE9_Eg -# sha256: 0bQJgvZSh-qLJ4YlsOMq7fvu7oNMDNZOCfDw2LYnNUE -django-browserid==1.0.1 -# sha256: EWYQoXCRjfNC2yxLr7yPS5F4DEmHj5KdieNg8QrSnSo -# sha256: deTJeG7SjRnsf7UA88ciGm64d0jLxvXi7qykw9aPMMs -factory_boy==2.6.0 - -# sha256: nNnjqXaEmjzYFoMNeBG5NUPB3J2p88sczC-dqDG4sCU -# sha256: kUtLh0-mJQoIl7wwtn4CA02SpyNatyqRv22km3F1HeQ -django-waffle==0.11 - -# sha256: s3dwm_pgykg2RcKcd6ZzVQy6Qmf6Lgbu4aDy9jRhlQY -django-cache-machine==0.9.1 - -# sha256: fFRhL9yCnkg8xTU_xiOZZbCk46IqnrYYwJAOh2QgCUk -# sha256: 2wDaXP_THYTiurzlFcVb2lOS7o0m8dmfXalFssMzqr4 -djangorestframework==3.1.0 - -# sha256: IWJc4ZHMtzT0e5DvkrYD0ENmu2fQ61K9BgqNduZwYBg -# sha256: f-zPgRqMLGC8In4NlWAV8Opgl8gEZg6PSkraDnSBfCQ -django-tastypie==0.12.2 - -# sha256: PISsHCzz4m_XMHMkTglKFfZQy6fSHPXz3Mh5zIduzWg -django-import-export==0.4.1 -# sha256: 2tzdwK77-Z7qIU4PEjK5Ty-pvZj6g1NxHayxEr_Luyo -# sha256: n_fGzEQ_jFGZSzSmZ7vPRa_W2UW-dHe1LpdRb9F8U6w -# sha256: 8b_--cvIJij259e0DX4lWu-qGttqGx0mxpqLeeYgipg -nose==1.3.7 -# sha256: Gbja9M1eZmA9xYAY8DhBFwl94YcUd1M4zbdqlNSNiWY -# sha256: kvdDM2cBF0bMPL09XmfcwRBmyLhWe5pRSUju6hH6WEM -django-nose==1.4.3 - -# sha256: fRdUe2UhbMXG-8BK7lUIjM1ZF8B3UwTZb3AXwmx4nNc -# sha256: AMxHk1r7vYMmD90oOwqnkOZY0qcZIgSfbkZ9yooSRTc -django-filter==0.11.0 -# sha256: MHG3HvjEMXis6ENQArEfL_Btt2kPB9lgVA6rf0GD3fc -# sha256: 0C8ZHD2SqFHJ0gKOkbryoPJzTNO2WXQ9NiQBHR73ptU -celery==3.1.20 - -# sha256: ev9Axs3acDGT-sYjFBDGwK1ZOc4FOhaAFlAemCx1XIQ -# sha256: P6kr8Bkq-Sb3oNm-Ax_j_Q-6oZktQs8vB-aPdqwYKI4 -statsd==3.2.1 - -# sha256: p_btn_nF2FR3h0N8oYnkbe1asdV1Igbusucz93LWEU8 -# sha256: HypmuAb01mSTXhq-eC9L7SrXWy0quWcM7QtJS66uiio -django-mozilla-product-details==0.9 - -# sha256: ET-7pVManjSUW302szoIToul0GZLcDyBp8Vy2RkZpbg -# sha256: xXeBXdAPE5QgP8ROuXlySwmPiCZKnviY7kW45enPWH8 -requests==2.9.1 - -# sha256: jZTPYnNgb3Z1P8sTJGI3krNzjHYSwrGAyFzF6IZC5WA -# sha256: pZ_bzsKPea2YQup_dM3loC4UowJYNuC8ZsT8SFls4so -Markdown==2.6.5 - -# sha256: kl8PE-Wh54Y-XtEXn9D28aUZ5aApNxS6dnRShqrX7QI -django-statsd-mozilla==0.3.16 - -# sha256: bHB519ZFVS-SH1LHYE4711C3rwKcuG8a5bDpLET5Ir4 -django-uuslug==1.1.7 -# sha256: 8s4emJsnLPy2d2FnY-Ci5-xlnv-meoiqkrOmVSj2Cjw -pycrypto==2.6.1 - -# sha256: Z0Gl1-i49TFRqr1H_T3xGVrtVAsWmy12hLkE_xErSDw -# sha256: hT0YypWKWYeHzrjCl6NpdFuCllvygnaGEnLxR0WXeZk -kombu==3.0.33 - -# sha256: 4O0M5rj_5WkKLoVseQjcVX4OYFKD1ohd0TYdefKSiQg -# sha256: LepNFtBzyQLDuJ2blmIPtnKawPepI7vHd8tK2CfAxho -amqp==1.4.9 - -# sha256: N4Ethjya0-NcBzTELgvwMgzow77YLNIK1UyzTRWBV7o -anyjson==0.3.3 - -# sha256: JJOe2M7J9sH65iMS3MqnHKu6968HxwpVk1x9NGOC0Qs -# sha256: eia8y7ppzZElWO3OpyaOY6sYS2-1I7EnOISLBQgwRnM -# sha256: B-d0FQU05hr3A1OxJi0r2tKcPzvuAfLHxzNz9c2W68Q -# sha256: K5TRcIdjLlkxgYYDGbznm3Ffqz8yGfDFfhRg-VgUHQ8 -# sha256: 2xGUg1kVSBJh9xMDxkVShgoAgMZh2mwZRzrvAU2lo5s -# sha256: -MX9gtZJU-pop81EfqKeST8wKrkGCtG6RSxU0uN7vS8 -# sha256: x11JBRBP0ZEwJd3V4JBOKLv0KE5PfCp7vUh9RT5HOCc -# sha256: DeOlJroe5vVhyeTdYLjDq5nRJx8tMjEKgZNud1g7n_0 -# sha256: 0hYYE4cxf4aWxtHICiSRJY0DdJPB8MbrWJkqVJSB534 -billiard==3.3.0.22 - -# sha256: xXA8wRwaaUdTbzzouzBnZri7WoSlNxf1pwPODxgjXkw -slugify==0.0.1 - -# sha256: sfDTXD0rLEDTOjEMqZ83IjJkh58uU8Y8Hsv67vwmkZ0 -# sha256: QcLa1_SR9VV-Ing6mvgb7WL3tvsNev1MLuMB8-tCjJM -tablib==0.10.0 - -# sha256: nbpWEfvyeJM0c0n9UcwZEctANoKnFjNzrazFZdEeLkw -diff-match-patch==20121119 - -# sha256: pLrqtgYUKCOdh8m2GfgUvGvWa5xMccAjMBpQ3cUZ7rQ -# sha256: PGmiHjfnf3VOb8CevacKzZLJDYpY8ppBzAJINRN43cM -python-mimeparse==0.1.4 - -# sha256: PpVEXB21AKNEB5pHsXHEXvGPV9GI3_2w5BZccb6o6z0 -# sha256: KuY89HXwvQSbci-sIIE9Yq7cFJV91aO_ANEg0rVARGA -python-dateutil==2.4.2 - -# sha256: AJ1XRZCu6_8jC9o9jBys2dSLIEzBp4Sl0742OHJ_jRY -# sha256: zP3fv5R6UciwlaXmO-1DAFonelWB3pLtNz6wa3XO7Hc -django-session-csrf==0.6 -# sha256: RTvHzC2NgI2O8Gv-ym2yMOTel1198X1DzF-Kc4rIlpE -# sha256: XHjgQW9jcnIjFKozu_lE02Gu83hwv1f3V5YIRrUtD7c -# sha256: QuOdbdEblhhW1DEAbwn2bwCvhWv8nMUCaei91q9FhqM -# sha256: xRJ0fBCHOXbl6_lBmna_W-dv8OSmJOMcoNTaoOk0jzM -# sha256: dbuAg7xvMvBaT-iwOTh09oAbaj82b7-LwDOHoLrtwE4 -# sha256: Aa5V6kuEMFZ2YpBlYcx9VgXcDQIwpx-eXTTOHJHaubQ -# sha256: LWAtaRgRRY9v2XCWrtVTfM0AKeEyIK_MLHZ9uEUCrf4 -# sha256: 5ZfCYBGxRMX9eta9xWAkEZX5X4bViavpgzotS9mmamk -# sha256: w39GpZ1EixjOpKltFvN-OMpMpXJMJ35ljzZ8K8u_NIQ -# sha256: 3eEbTCETOK8cUV4i5vimNYoYhWjWzpLEy_Te1YTIC8c -# sha256: XFIVu-dyuMMIzxLlVSMNCrXiWjwX5FGDnkJkFbck38w -# sha256: Vi0RmCN3VuvwLgiGEVeuuOVQBFjhFYAic2DotKerDpg -# sha256: iy0ZfvIg0Q63RiXd5687ENqpc66aHq3WNm92P61Dh_o -bcrypt==2.0.0 -# sha256: _vV3twBVxGp_K5W23C-_iG5sp9ZgChorQHGbO63L9gU -# sha256: US0Ha1wblVCLoKNqpTX0LFMX0D1lWnt_gDdops7uEyA -# sha256: exfXaBN6urb1gTDKG46hUm20lJ7SYPhjvVlJ9kG6t4g -# sha256: wLwWSljfsZkdSHMDpjP1rokdiUhKfl5kpWSOXlOv0LA -# sha256: Ubcz7wVneBLd31Rr0s-So_cFKlRKkyWxKqqZZA8yWYk -# sha256: 2eAOeXtCb2kcdIF0ZFe_rI2FZoQJyqm2P1ClTm7ySBo -# sha256: AEIYlC6zX-9_92ZFKg-WbCq2r4IgG0NM0axMU1HBqAI -# sha256: OoS_5jtegdJePL9TF4pq5iU1LCBJVEIr59dQd2w0Sq0 -# sha256: ulqt4lfvua5Us2rjuNWqt8qXFx1yZJEbAjaVJaTP7Ig -# sha256: hO9QHl5dca5YfLymC8aCD3pdz0WPYyrdEfmr3y1wSHQ -# sha256: bsFIF5jjb3clEFtUaJzs8nvrWFZ20XMUyPHCeIxGmfA -# sha256: wF5DtdvB5qo_a3L-hQ14KSPc_QQlzjeniFa9v0Pe-mo -# sha256: OSHSM3wtJbchH8PndbuWmlWmtqmVa3YTMzIQ8gjRcyM -# sha256: edtZQGW-FeYXqZLaBbPzkcmIrqQX5OhB-jrxiJCBLOs -# sha256: 14mkUgyUUbbknQr6o290pzjkKOPlvzn-kRz-MjHzBdE -# sha256: vAU8oatd6EP7478rt1O-aJg17HBCfEQ8swhM-jrjLvY -# sha256: EXWJZVRg6O85iHa6zdUBHj4lQjYLy-iSzbsrv8QZdQU -# sha256: DXmcW7k_-1kDaJQXOXurvdPegFgMMbVcW44n9zp_rgM -# sha256: NxDdoHt8f930HhtbT_nkn7MmB1JI6DG3Gh8OTvGlzhw -# sha256: -USg_N9Ja209aYcuYnyEF-1GBm4W_6YB5DV70R4-Pg0 -# sha256: RPdvbD_GVIYIIXhRkuyim9ZlMa9X0JtoHm1SWEYEp-c -cffi==1.5.0 -# sha256: 0t5OOpP794nu-Xm0VcCWLIPbVwIoWGVL9OWW00-ePLE -# sha256: sWGCYHmODCbvUsjmwU1EV4N1bsqS8qsbjbi-6OzJuMM -django-compressor==2.0 -# sha256: JUqVsKQ4bfH9lJgjlC9jEsgPujyIxe-tecrYZIvF_rU -django-celery==3.1.17 -# sha256: gqJTJFrtmHXxQd2Yp0YlcrypPQZM8xF70z3kth7hjmg -# sha256: LCZCVPbPzmTDvZ1IiFIICTpzuQlWRfYA3gonfOAeoOU -# sha256: h9QBPQYl1HiaT1a415oE1c5tsRUrtl8dOXRPdwmjZrQ -beautifulsoup4==4.4.1 -# sha256: JhKhkajVhCv6BX5BulC7udy3IkGdJAjHjP9HWNB1SGg -html5lib==0.9999999 -# sha256: D_eMQD2bzPWkJabTGhKqa0fxwhyk3CVzp-LzKpczXrE -# sha256: EF-NaGFvgkjiS_DpNy7wTTzBAQTxmA9U1Xss5zpa1Wo -six==1.10.0 -# sha256: eVm0p0q9wnsxL-0cIebK-TCc4LKeqGtZH9Lpns3yf3M -pycparser==2.14 -# sha256: R_G7PqhY0ABz4-FVCoisIHCWYDROlycXjWBHjE07uU4 -python-slugify==1.2.0 -# sha256: UUd2RqkWlGnjfnkbE65l_Mdbf39XDQ0-UU0HeAXALh4 -Unidecode==0.04.19 -# sha256: uhN1-xAk6OkVR1BNQ5IyF5XJif3lALluvHyTiE94bmA -# sha256: PZvJY9gAiuFR1sZk-f1VRCcF6puebXznfN1Av5LZHzo -django-appconf==1.0.1 -# sha256: 8jnKrirtDi3N1pX_zeRXKVjAAbS9xGSpKR0bDK4Mqo0 -pyLibravatar==1.7 -# sha256: O2iaE1r7L9ttoimNUhN4D0Oj8hDPjJYNULJSrLP2KQI -# sha256: VEQZ_V4zMAg13jsvhGKhRd2kUdXjCIpp4M0NorW8D3o -python-memcached==1.57 -# sha256: yoe2ldPXhkFXdzphJj5au5YAbp_w4CHv-Qy-DhuhgnA -rcssmin==1.0.6 -# sha256: 3ZWRqnNQCwi32yQ2f40yxkcAIfOdWrTlDHwC5EAThvE -rjsmin==1.0.12 -# sha256: k1cSgAzkdgcB2JrWd2Zs1SaR_S9vCzQMi0I5o8F5iKU -# sha256: WjGCsyKnBlJcRigspvBk0noCz_vUSfn0dBbx3JaqcbA -ipaddress==1.0.16 -# sha256: QF2zLq6jbrli5hMOTQCHdi1QAYsUI3sCyMT-lm1WFbI -# sha256: YIuE10Z4F4HKyrX6P3pS_s5GBHsD9Ohhmt484m0MhPI -fake-factory==0.5.7 -# sha256: 8ZYNi_8Kr62SUrnoAnl0j-fr5ecZSH0TLMsSgVI822I -pyDNS==2.3.6 -# sha256: n4KpA86-rHgRg7cKHnuummALFPLnGHalvaeUTautX0w -https://github.com/mozilla/happyforms/archive/master.zip#egg=happyforms -# sha256: MxjtKWAkDWHLxlWIWO4AwQ7td6ZQjE0e2Ob39IOZyXU -# sha256: xTXEQDgC9us4FzzUhj5BniJ0khoBqKrYpbSXwTHGKHU -Babel==2.3.4 -# sha256: br3J4K0Yj50bLN2bxZy-Qr-TGHXoKeelleazq9wFzfs -# sha256: CrLGK4eYfjJS-J0wt87b7BKgGvknSvn_pIEI8sE8YGI -imagesize==0.7.1 -# sha256: OxNQQo3tQ4uqhTRD9zfPOTHrRggCTveBe2Dl3bp0nT8 -commonware==0.4.2 -# sha256: 7PtznHX-9-MbfAyCzpG10swOn2oUs97FlaummAxzHP0 -https://github.com/mozilla/nuggets/archive/master.zip#egg=nuggets -# sha256: iIooDyTc_yzFWsDlH8dkSEsoT5H_cRohnkWabV923is -# sha256: MOUA8TkhxzyW9Jpt0q373ZK8P64SGD6RsGsuRYNIYgY -contextlib2==0.5.3 -# sha256: Lvg31Tuabr1U2oCI7ukDKgW7PsJ6elyvPc5smOEJKBE -# sha256: XSiQvvIil-DXhKhLETonqjv9hrZgxUG8CeA1oLX3MKQ -raven==5.18.0 +Django==1.8.13 \ + --hash=sha256:dad5da0cd7f3cca7da3ac42a19abba30f5cc10fae4976e474051e7085b4e95d1 \ + --hash=sha256:128e8bdc11c69ea90f778435d38126453d3bf283dbd28cf15a33aa8e52245df4 +Jinja2==2.8 \ + --hash=sha256:1cc03ef32b64be19e0a5b54578dd790906a34943fe9102cfdae0d4495bd536b4 \ + --hash=sha256:bc1ff2ff88dbfacefde4ddde471d1417d3b304e8df103a7a9437d47269201bf4 +MarkupSafe==0.23 \ + --hash=sha256:a4ec1aff59b95a14b45eb2e23761a0179e98319da5a7eb76b56ea8cdc7b871c3 +django-csp==2.0.3 \ + --hash=sha256:d32576baad77fdd9e6859be97c2b6a3c17a6f484364389db731f0697b7a19661 +pytz==2015.4 \ + --hash=sha256:9966d2c3f86b23374da9890584f16b03f6e46055824e30a108716974d45d1e45 \ + --hash=sha256:f7c5be4db86f1a76ccc4c807f9008536275abec3e2815d18265d694aeef3cb3c \ + --hash=sha256:6ce918e48daed649e13aa4eb2ce99d80212411401be42288c94473a81109244e \ + --hash=sha256:6441e798f8863744c90574de253bed328c477fd2c86caf52b8d9d85f1ecb9f96 \ + --hash=sha256:4d64ed1b9e0e73095f5cfa87f0e97ddb4c840049e8efeb7e63b46118ba1d623a \ + --hash=sha256:39ef0541e489b1cf5515cedf1b1f99de585fd660463cdcde43a03d7d92042782 \ + --hash=sha256:685b1994026572177458af143cc24a2082f400f18e63fdc8e2ed68dd93bb0f49 \ + --hash=sha256:8446013dfbde9b3f4f9bfea09418ddd526760a8798c41badd3f707a98ad0745b \ + --hash=sha256:d5ff706089e9150760d238b66203a24d4a0a6175160410191995ff04d1893faa \ + --hash=sha256:a78b484d5472dd8c688f8b3eee18646a25c66ce45b2c26652850f6af9ce52b17 \ + --hash=sha256:c4ee70cb407f9284517ac368f121cf0796a7134b961e53d9daf1aaae8f44fb90 \ + --hash=sha256:00f35fb0e7e2fc348cd091d25d36c6768b721463d12396d68a9491a8a5f91ecf +newrelic==2.52.0.40 \ + --hash=sha256:59b515bb7dbbcdd307f817ac38854e80839ebfa33c5db797f65450793c945c0d +Pygments==2.0.2 \ + --hash=sha256:0a3a2265e1efb0defa722d1f429730dc9df975adc3c60ac7d2b432bdf03c041d \ + --hash=sha256:5ded2fa9094fd7dfeb3da92636409fd702a0d07d606283504d7ee04401cee5cb \ + --hash=sha256:7320919084e6dac8f4540638a46447a3bd730fca172afc17d2c03eed22cf4f51 +django-jinja==2.1.2 \ + --hash=sha256:fc2b6a65201b27711e800eb2e1262ff42a32f027bd2267e34121f1bd2bd6b933 +MySQL-python==1.2.5 \ + --hash=sha256:ab22d1322099098730a57fd59d610f60738f95a1cb68dacca2d1c47cb0cbe8ee \ + --hash=sha256:811040b647e5d5686f84db415efd697e6250008b112b6909ba77ac059e140c74 +django-sha2==0.4 \ + --hash=sha256:b06a86451a99d6c26bfa65082999dbf658e087e596e9292cce688b53eabaaada +django-browserid==1.0.1 \ + --hash=sha256:c612cb27ea867176c424a43059e1e058f17c3eed2b635c088413e552113dfc48 \ + --hash=sha256:d1b40982f65287ea8b278625b0e32aedfbeeee834c0cd64e09f0f0d8b6273541 +factory_boy==2.6.0 \ + --hash=sha256:116610a170918df342db2c4bafbc8f4b91780c49878f929d89e360f10ad29d2a \ + --hash=sha256:75e4c9786ed28d19ec7fb500f3c7221a6eb87748cbc6f5e2eeaca4c3d68f30cb +django-waffle==0.11 \ + --hash=sha256:9cd9e3a976849a3cd816830d7811b93543c1dc9da9f3cb1ccc2f9da831b8b025 \ + --hash=sha256:914b4b874fa6250a0897bc30b67e02034d92a7235ab72a91bf6da49b71751de4 +django-cache-machine==0.9.1 \ + --hash=sha256:b377709bfa60ca483645c29c77a673550cba4267fa2e06eee1a0f2f634619506 +djangorestframework==3.1.0 \ + --hash=sha256:7c54612fdc829e483cc5353fc6239965b0a4e3a22a9eb618c0900e8764200949 \ + --hash=sha256:db00da5cffd31d84e2babce515c55bda5392ee8d26f1d99f5da945b2c333aabe +django-tastypie==0.12.2 \ + --hash=sha256:21625ce191ccb734f47b90ef92b603d04366bb67d0eb52bd060a8d76e6706018 \ + --hash=sha256:7feccf811a8c2c60bc227e0d956015f0ea6097c804660e8f4a4ada0e74817c24 +port==0.3.2 \ + --hash=sha256:4fcbffe5c64346dbf57b7640c5e64a9dcd3a20679e52cc3edaeaf2e41285cd7d +nose==1.3.7 \ + --hash=sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a \ + --hash=sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac \ + --hash=sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98 +django-nose==1.4.3 \ + --hash=sha256:19b8daf4cd5e66603dc58018f0384117097de18714775338cdb76a94d48d8966 \ + --hash=sha256:92f7433367011746cc3cbd3d5e67dcc11066c8b8567b9a514948eeea11fa5843 +django-filter==0.11.0 \ + --hash=sha256:7d17547b65216cc5c6fbc04aee55088ccd5917c0775304d96f7017c26c789cd7 \ + --hash=sha256:00cc47935afbbd83260fdd283b0aa790e658d2a71922049f6e467dca8a124537 +celery==3.1.20 \ + --hash=sha256:3071b71ef8c43178ace8435002b11f2ff06db7690f07d960540eab7f4183ddf7 \ + --hash=sha256:d02f191c3d92a851c9d2028e91baf2a0f2734cd3b659743d3624011d1ef7a6d5 +statsd==3.2.1 \ + --hash=sha256:7aff40c6cdda703193fac6231410c6c0ad5939ce053a168016501e982c755c84 \ + --hash=sha256:3fa92bf0192af926f7a0d9be031fe3fd0fbaa1992d42cf2f07e68f76ac18288e +django-mozilla-product-details==0.9 \ + --hash=sha256:a7f6ed9ff9c5d8547787437ca189e46ded5ab1d5752206eeb2e733f772d6114f \ + --hash=sha256:1f2a66b806f4d664935e1abe782f4bed2ad75b2d2ab9670ced0b494baeae8a2a +requests==2.9.1 \ + --hash=sha256:113fbba5531a9e34945b7d36b33a084e8ba5d0664b703c81a7c572d91919a5b8 \ + --hash=sha256:c577815dd00f1394203fc44eb979724b098f88264a9ef898ee45b8e5e9cf587f +Markdown==2.6.5 \ + --hash=sha256:8d94cf6273606f76753fcb1324623792b3738c7612c2b180c85cc5e88642e560 \ + --hash=sha256:a59fdbcec28f79ad9842ea7f74cde5a02e14a3025836e0bc66c4fc48596ce2ca +django-statsd-mozilla==0.3.16 \ + --hash=sha256:925f0f13e5a1e7863e5ed1179fd0f6f1a519e5a0293714ba76745286aad7ed02 +django-uuslug==1.1.7 \ + --hash=sha256:6c7079d7d645552f921f52c7604e3bd750b7af029cb86f1ae5b0e92c44f922be +pycrypto==2.6.1 \ + --hash=sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c +kombu==3.0.33 \ + --hash=sha256:6741a5d7e8b8f53151aabd47fd3df1195aed540b169b2d7684b904ff112b483c \ + --hash=sha256:853d18ca958a598787ceb8c297a369745b82965bf28276861272f14745977999 +amqp==1.4.9 \ + --hash=sha256:e0ed0ce6b8ffe5690a2e856c7908dc557e0e605283d6885dd1361d79f2928908 \ + --hash=sha256:2dea4d16d073c902c3b89d9b96620fb6729ac0f7a923bbc777cb4ad827c0c61a +anyjson==0.3.3 \ + --hash=sha256:37812d863c9ad3e35c0734c42e0bf0320ce8c3bed82cd20ad54cb34d158157ba +billiard==3.3.0.22 \ + --hash=sha256:24939ed8cec9f6c1fae62312dccaa71cabbaf7af07c70a55935c7d346382d10b \ + --hash=sha256:7a26bccbba69cd912558edcea7268e63ab184b6fb523b12738848b0508304673 \ + --hash=sha256:07e774150534e61af70353b1262d2bdad29c3f3bee01f2c7c73373f5cd96ebc4 \ + --hash=sha256:2b94d17087632e593181860319bce79b715fab3f3219f0c57e1460f958141d0f \ + --hash=sha256:db1194835915481261f71303c64552860a0080c661da6c19473aef014da5a39b \ + --hash=sha256:f8c5fd82d64953ea68a7cd447ea29e493f302ab9060ad1ba452c54d2e37bbd2f \ + --hash=sha256:c75d4905104fd1913025ddd5e0904e28bbf4284e4f7c2a7bbd487d453e473827 \ + --hash=sha256:0de3a526ba1ee6f561c9e4dd60b8c3ab99d1271f2d32310a81936e77583b9ffd \ + --hash=sha256:d216181387317f8696c6d1c80a2491258d037493c1f0c6eb58992a549481e77e +slugify==0.0.1 \ + --hash=sha256:c5703cc11c1a6947536f3ce8bb306766b8bb5a84a53717f5a703ce0f18235e4c +tablib==0.10.0 \ + --hash=sha256:b1f0d35c3d2b2c40d33a310ca99f37223264879f2e53c63c1ecbfaeefc26919d \ + --hash=sha256:41c2dad7f491f5557e22783a9af81bed62f7b6fb0d7afd4c2ee301f3eb428c93 +diff-match-patch==20121119 \ + --hash=sha256:9dba5611fbf27893347349fd51cc1911cb403682a7163373adacc565d11e2e4c +python-mimeparse==0.1.4 \ + --hash=sha256:a4baeab6061428239d87c9b619f814bc6bd66b9c4c71c023301a50ddc519eeb4 \ + --hash=sha256:3c69a21e37e77f754e6fc09ebda70acd92c90d8a58f29a41cc0248351378ddc3 +python-dateutil==2.4.2 \ + --hash=sha256:3e95445c1db500a344079a47b171c45ef18f57d188dffdb0e4165c71bea8eb3d \ + --hash=sha256:2ae63cf475f0bd049b722fac20813d62aedc14957dd5a3bf00d120d2b5404460 +django-session-csrf==0.6 \ + --hash=sha256:009d574590aeebff230bda3d8c1cacd9d48b204cc1a784a5d3be3638727f8d16 \ + --hash=sha256:ccfddfbf947a51c8b095a5e63bed43005a277a5581de92ed373eb06b75ceec77 +bcrypt==2.0.0 \ + --hash=sha256:453bc7cc2d8d808d8ef06bfeca6db230e4de975d7df17d43cc5f8a738ac89691 \ + --hash=sha256:5c78e0416f6372722314aa33bbf944d361aef37870bf57f757960846b52d0fb7 \ + --hash=sha256:42e39d6dd11b961856d431006f09f66f00af856bfc9cc50269e8bdd6af4586a3 \ + --hash=sha256:c512747c10873976e5ebf9419a76bf5be76ff0e4a624e31ca0d4daa0e9348f33 \ + --hash=sha256:75bb8083bc6f32f05a4fe8b0393874f6801b6a3f366fbf8bc03387a0baedc04e \ + --hash=sha256:01ae55ea4b8430567662906561cc7d5605dc0d0230a71f9e5d34ce1c91dab9b4 \ + --hash=sha256:2d602d691811458f6fd97096aed5537ccd0029e13220afcc2c767db84502adfe \ + --hash=sha256:e597c26011b144c5fd7ad6bdc560241195f95f86d589abe9833a2d4bd9a66a69 \ + --hash=sha256:c37f46a59d448b18cea4a96d16f37e38ca4ca5724c277e658f367c2bcbbf3484 \ + --hash=sha256:dde11b4c211338af1c515e22e6f8a6358a188568d6ce92c4cbf4ded584c80bc7 \ + --hash=sha256:5c5215bbe772b8c308cf12e555230d0ab5e25a3c17e451839e426415b724dfcc \ + --hash=sha256:562d1198237756ebf02e08861157aeb8e5500458e11580227360e8b4a7ab0e98 \ + --hash=sha256:8b2d197ef220d10eb74625dde7af3b10daa973ae9a1eadd6366f763fad4387fa +cffi==1.5.0 \ + --hash=sha256:fef577b70055c46a7f2b95b6dc2fbf886e6ca7d6600a1a2b40719b3badcbf605 \ + --hash=sha256:512d076b5c1b95508ba0a36aa535f42c5317d03d655a7b7f803768a6ceee1320 \ + --hash=sha256:7b17d768137abab6f58130ca1b8ea1526db4949ed260f863bd5949f641bab788 \ + --hash=sha256:c0bc164a58dfb1991d487303a633f5ae891d89484a7e5e64a5648e5e53afd0b0 \ + --hash=sha256:51b733ef05677812dddf546bd2cf92a3f7052a544a9325b12aaa99640f325989 \ + --hash=sha256:d9e00e797b426f691c7481746457bfac8d85668409caa9b63f50a54e6ef2481a \ + --hash=sha256:004218942eb35fef7ff766452a0f966c2ab6af82201b434cd1ac4c5351c1a802 \ + --hash=sha256:3a84bfe63b5e81d25e3cbf53178a6ae625352c204954422be7d750776c344aad \ + --hash=sha256:ba5aade257efb9ae54b36ae3b8d5aab7ca97171d7264911b02369525a4cfec88 \ + --hash=sha256:84ef501e5e5d71ae587cbca60bc6820f7a5dcf458f632add11f9abdf2d704874 \ + --hash=sha256:6ec1481798e36f7725105b54689cecf27beb585676d17314c8f1c2788c4699f0 \ + --hash=sha256:c05e43b5dbc1e6aa3f6b72fe850d782923dcfd0425ce37a78856bdbf43defa6a \ + --hash=sha256:3921d2337c2d25b7211fc3e775bb969a55a6b6a9956b7613333210f208d17323 \ + --hash=sha256:79db594065be15e617a992da05b3f391c988aea417e4e841fa3af18890812ceb \ + --hash=sha256:d789a4520c9451b6e49d0afaa36f74a738e428e3e5bf39fe911cfe3231f305d1 \ + --hash=sha256:bc053ca1ab5de843fbe3bf2bb753be689835ec70427c443cb3084cfa3ae32ef6 \ + --hash=sha256:117589655460e8ef398876bacdd5011e3e2542360bcbe892cdbb2bbfc4197505 \ + --hash=sha256:0d799c5bb93ffb5903689417397babbdd3de80580c31b55c5b8e27f73a7fae03 \ + --hash=sha256:3710dda07b7c7fddf41e1b5b4ff9e49fb326075248e831b71a1f0e4ef1a5ce1c \ + --hash=sha256:f944a0fcdf496b6d3d69872e627c8417ed46066e16ffa601e4357bd11e3e3e0d \ + --hash=sha256:44f76f6c3fc654860821785192eca29bd66531af57d09b681e6d52584604a7e7 +django-compressor==2.0 \ + --hash=sha256:d2de4e3a93fbf789eef979b455c0962c83db57022858654bf4e596d34f9e3cb1 \ + --hash=sha256:b1618260798e0c26ef52c8e6c14d445783756eca92f2ab1b8db8bee8ecc9b8c3 +django-celery==3.1.17 \ + --hash=sha256:254a95b0a4386df1fd949823942f6312c80fba3c88c5efad79cad8648bc5feb5 +beautifulsoup4==4.4.1 \ + --hash=sha256:82a253245aed9875f141dd98a7462572bca93d064cf3117bd33de4b61ee18e68 \ + --hash=sha256:2c264254f6cfce64c3bd9d48885208093a73b9095645f600de0a277ce01ea0e5 \ + --hash=sha256:87d4013d0625d4789a4f56b8d79a04d5ce6db1152bb65f1d39744f7709a366b4 +html5lib==0.9999999 \ + --hash=sha256:2612a191a8d5842bfa057e41ba50bbb9dcb722419d2408c78cff4758d0754868 +six==1.10.0 \ + --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ + --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a +pycparser==2.14 \ + --hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 +python-slugify==1.2.0 \ + --hash=sha256:47f1bb3ea858d00073e3e1550a88ac20709660344e9727178d60478c4d3bb94e +Unidecode==0.04.19 \ + --hash=sha256:51477646a9169469e37e791b13ae65fcc75b7f7f570d0d3e514d077805c02e1e +django-appconf==1.0.1 \ + --hash=sha256:ba1375fb1024e8e91547504d4392321795c989fde500b96ebc7c93884f786e60 \ + --hash=sha256:3d9bc963d8008ae151d6c664f9fd55442705ea9b9e6d7ce77cdd40bf92d91f3a +pyLibravatar==1.7 \ + --hash=sha256:f239caae2aed0e2dcdd695ffcde4572958c001b4bdc464a9291d1b0cae0caa8d +python-memcached==1.57 \ + --hash=sha256:3b689a135afb2fdb6da2298d5213780f43a3f210cf8c960d50b252acb3f62902 \ + --hash=sha256:544419fd5e33300835de3b2f8462a145dda451d5e3088a69e0cd0da2b5bc0f7a +rcssmin==1.0.6 \ + --hash=sha256:ca87b695d3d7864157773a61263e5abb96006e9ff0e021eff90cbe0e1ba18270 +rjsmin==1.0.12 \ + --hash=sha256:dd9591aa73500b08b7db24367f8d32c6470021f39d5ab4e50c7c02e4401386f1 +ipaddress==1.0.16 \ + --hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \ + --hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0 +fake-factory==0.5.7 \ + --hash=sha256:405db32eaea36eb962e6130e4d0087762d50018b14237b02c8c4fe966d5615b2 \ + --hash=sha256:608b84d746781781cacab5fa3f7a52fece46047b03f4e8619ade3ce26d0c84f2 +pyDNS==2.3.6 \ + --hash=sha256:f1960d8bff0aafad9252b9e80279748fe7ebe5e719487d132ccb1281523cdb62 +https://github.com/mozilla/happyforms/archive/master.zip#egg=happyforms \ + --hash=sha256:9f82a903cebeac781183b70a1e7bae9a600b14f2e71876a5bda7944dabad5f4c +Babel==2.3.4 \ + --hash=sha256:3318ed2960240d61cbc6558858ee00c10eed77a6508c4d1ed8e6f7f48399c975 \ + --hash=sha256:c535c4403802f6eb38173cd4863e419e2274921a01a8aad8a5b497c131c62875 +imagesize==0.7.1 \ + --hash=sha256:6ebdc9e0ad188f9d1b2cdd9bc59cbe42bf931875e829e7a595e6b3abdc05cdfb \ + --hash=sha256:0ab2c62b87987e3252f89d30b7cedbec12a01af9274af9ffa48108f2c13c6062 +commonware==0.4.2 \ + --hash=sha256:3b1350428ded438baa853443f737cf3931eb4608024ef7817b60e5ddba749d3f +https://github.com/mozilla/nuggets/archive/master.zip#egg=nuggets \ + --hash=sha256:ecfb739c75fef7e31b7c0c82ce91b5d2cc0e9f6a14b3dec595aba6980c731cfd +contextlib2==0.5.3 \ + --hash=sha256:888a280f24dcff2cc55ac0e51fc764484b284f91ff711a219e459a6d5f76de2b \ + --hash=sha256:30e500f13921c73c96f49a6dd2adfbdd92bc3fae12183e91b06b2e4583486206 +raven==5.18.0 \ + --hash=sha256:2ef837d53b9a6ebd54da8088eee9032a05bb3ec27a7a5caf3dce6c98e1092811 \ + --hash=sha256:5d2890bef22297e0d784a84b113a27aa3bfd86b660c541bc09e035a0b5f730a4 +hashin==0.6.0 \ + --hash=sha256:8d54551aae64dc8c135dfd5c89efd81a5014f7ce79da642b3fc3eef44ccf7a26 \ + --hash=sha256:7563490ec8c9c361e48c7623898b3585143d379714a03bee4c3d52fb13e85086 +django-import-export==0.4.5 \ + --hash=sha256:0406f781a771c39e325474df09644a5cea5188dfac7ecace4f32d664920abd00