diff --git a/.travis.yml b/.travis.yml index 2ec1dd9..226b1a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,10 @@ script: py.test --cov news env: - SECRET_KEY=FOO DEBUG=False ALLOWED_HOSTS=* DATABASE_URL=mysql://root@localhost/basket SUPERTOKEN='foo' install: - - bin/peep.py install -r requirements/compiled.txt -r requirements/dev.txt - - pip install coveralls + - bin/pipstrap.py + - pip install --require-hashes -r requirements/travis.txt -r requirements/dev.txt after_success: + - pip install coveralls - coveralls notifications: irc: diff --git a/Dockerfile b/Dockerfile index 8c3d956..543efe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,10 +10,13 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends build-essential python2.7 libpython2.7 python-dev \ python-pip gettext python-mysqldb +# Get pip 8 +COPY bin/pipstrap.py bin/pipstrap.py +RUN bin/pipstrap.py + # Install app -COPY bin/peep.py bin/peep.py COPY requirements/base.txt requirements/prod.txt /app/requirements/ -RUN bin/peep.py install -r requirements/prod.txt +RUN pip install --require-hashes -r requirements/prod.txt COPY . /app diff --git a/bin/peep.py b/bin/peep.py deleted file mode 100755 index 67537e9..0000000 --- a/bin/peep.py +++ /dev/null @@ -1,904 +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 -import cgi -from collections import defaultdict -from functools import wraps -from hashlib import sha256 -from itertools import chain -from linecache import getline -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, 4, 1 - - -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 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 - - -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', 'use_wheel', '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 - - def _path_and_line(self): - """Return the path and line number of the file from which our - InstallRequirement came. - - """ - path, line = (re.match(r'-r (.*) \(line (\d+)\)$', - self._req.comes_from).groups()) - return path, int(line) - - @memoize # Avoid hitting the file[cache] over and over. - def _expected_hashes(self): - """Return a list of known-good hashes for this package.""" - - def hashes_above(path, line_number): - """Yield hashes from contiguous comment lines before line - ``line_number``. - - """ - for line_number in xrange(line_number - 1, 0, -1): - line = getline(path, line_number) - match = HASH_COMMENT_RE.match(line) - if match: - yield match.groupdict()['hash'] - elif not line.lstrip().startswith('#'): - # If we hit a non-comment line, abort - break - - hashes = list(hashes_above(*self._path_and_line())) - hashes.reverse() # because we read them backwards - return hashes - - 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 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) - - def downloaded_reqs(parsed_reqs): - """Just avoid repeating this list comp.""" - return [DownloadedReq(req, argv, finder) for req in parsed_reqs] - - try: - return downloaded_reqs(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 downloaded_reqs(parse_requirements( - path, options=EmptyOptions(), session=PipSession(), finder=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 main(): - """Be the top-level entrypoint. Return a shell status code.""" - commands = {'hash': peep_hash, - 'install': peep_install} - 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 0000000..7df95d2 --- /dev/null +++ b/bin/pipstrap.py @@ -0,0 +1,115 @@ +#!/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.7 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 +from subprocess import check_output +from sys import exit +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 + + +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 + 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: + packages = [ + # Pip has no dependencies, as it vendors everything: + ('https://pypi.python.org/packages/source/p/pip/pip-8.0.2.tar.gz', + '46f4bd0d8dfd51125a554568d646fe4200a3c2c6c36b9f2d06d2212148439521'), + # This version of setuptools has only optional dependencies: + ('https://pypi.python.org/packages/source/s/setuptools/setuptools-19.4.tar.gz', + '214bf29933f47cf25e6faa569f710731728a07a19cae91ea64f826051f68a8cf'), + # We require Python 2.7 or later because we don't support wheel's + # conditional dep on argparse. This version of wheel has no other + # dependencies: + ('https://pypi.python.org/packages/source/w/wheel/wheel-0.26.0.tar.gz', + 'eaad353805c180a47545a256e6508835b65a8e830ba1093ed8162f19a50a530c') + ] + downloads = [hashed_download(package[0], temp, package[1]) + for package 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/requirements/base.txt b/requirements/base.txt index e1e608b..334e5f4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,106 +1,80 @@ -# sha256: BYFpY8u63RMbzuj7UGnYaVeW4l0IHsJO_2K8H8jtiRo -# sha256: glUkL6DZ4L8zElmmvbgTZJM6y-iGMpFmFVj_2y_J7XA -Django==1.8.8 - -# sha256: eI_6Kd-3VJAOCLkIgffqFsPCXKKUlUjup7Q9LKc54z8 -# sha256: -65f6zOuk5TCdbyDSrlGhLlN4DrMjzY4e9C_DFHvKO4 -statsd==3.1 - -# sha256: SPn2n5cJiLPfLIpWhNqItS8RXhleyluJYXLbD4wCYOM -basket-client==0.3.11 - -# sha256: ygF2j97N4TQwHzFwdDIm9g7f9cOTXxJDc3jr2RFQY1M -# sha256: 8uJz7TSsu1YJYtXPEpF5NtjfAil98JvTCJuFRtRYQTg -dj-database-url==0.3.0 - -# sha256: cVBsEnq6Vmqxubd78JzNMRmjh663bfGzTyVBzkdkkBw -# sha256: NYPKHJtDsIudxxRjpcAv2ubr697oEqZxmOKn5CnFQhA -django-cache-url==1.0.0 - -# sha256: _NluK-R8ju80xlDgB6bVRuGefuYQQbie27u-dhmqOYc -django-cors-headers==1.1.0 - -# sha256: QpaQscacvWXGxZLGbRWS-KHPPVKCEDCDcx-pDqU8F8I -django-jsonfield==0.9.15 -# sha256: WN0CwygZupfEB4uMEziI0w9_vTk3KW_9rVD3n7V9XNs -django-mozilla-product-details==0.8.1 - -# sha256: 2ZQmvkb9kv5rZLn1xZJuqYWTlOROw--eH3xkfTomZX8 -django-picklefield==0.3.1 - -# sha256: 9ztTssT9NCy9S5g__DP9z4-Kb1ldVIhl6mPIOe84KTw -# sha256: k9q80xMaiNb9Gf9N4Kg1HtRLTslD6NWNJzBvf6-IIa8 -django-ratelimit==0.6.0 - -# sha256: 9POHfkrOhFHe13ZPf0twRZ2kBdc6WnKijw0j9QCAKR0 -django-sslify-admin==0.5.0 - -# sha256: wOkPMEu_bxD2PgAZECmAyVDcaDMm0qTlDnGZ3sxDw2o -django-statsd-mozilla==0.3.15 - -# sha256: aUBxjfw-_0JYIDrVAhCQkz5cBHB9XKjMnnPJSniU6p8 -pathlib==1.0.1 - -# sha256: bxlzSLRvuM358_z8Kn1al9qV2z4uhmfPZXIWJ0_hsAk -python-dateutil==1.5 - -# sha256: ufv3R_qOcR0zDy9Da5suz17tnrZ6Y3yoGrzRHCq-3sg -python-decouple==2.3 - -# sha256: TWTtG54OcwlfXPqH8Ol920yEAEno7-t-Y7RhGLodYjo -# sha256: p4tITVRy3Yxoj4s-7hhkaiXGbORbLCZlKFD2r5zlKxc -pytz==2015.4 - -# sha256: IPl2zc4CpCtpzoDp4DiXpRgUs21EizcohUYIbrxHMUY -# sha256: OYo9ttYYmdJf1KBsbKEgUbDOFx1wXezX7VURUXtLuT0 -requests==2.7.0 - -# sha256: QYqTw5en7asj5ViNvAZ6x0pyPts9VBvUk295R252Rdo -# sha256: 4kBSQR_E-9H2cmNVN8P8IzDZSBsYwDF2lbRiWVEskdU -six==1.9.0 - -# sha256: 1VJK5SO7ngnFe829HvriwofSBgNojqMfYCDtGApImvA -suds==0.4 - -# sha256: Evi5g5C-_EczbSwOW62cxIYJ2Ajquz-GddwQJ6Op6ds -WebOb==1.4.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: aaTNrvMmjYjy3G-Lj1w6E5Vf49ql9ZdGqBfc-JG-WJk -ua-parser==0.5.1 - -# sha256: gr4ORO1AwOr4fqDoeiT8jwr92OsU_7R4qtAssp4-7Sw -https://github.com/selwin/python-user-agents/archive/ed386c818ff7a10849348711d4bdd00506088702.tar.gz#egg=user-agents==1.0.1 - -# sha256: 68_IZ95aaPn1uhTRHbrYjmr_hDWo05M51c6w5bBt5kA -# sha256: 406XZaYSD0ZkwSD0GYp4bDmg-35KWb0ZotbjqIS2OIk -amqp==1.4.6 - -# sha256: N4Ethjya0-NcBzTELgvwMgzow77YLNIK1UyzTRWBV7o -anyjson==0.3.3 - -# sha256: aI-UZrHDrhQQY4Hm29MoEV51xSYMVC60jmxGkx9pKMw -billiard==3.3.0.20 - -# sha256: CST5QHDG_FfUCLFphIxbOIMmaP_-Bg5ItIA_sj4OPq8 -# sha256: 2_WWGNWp7_Fy0lAh82YUvlrwUB5FJ5dcpQS5WGPxT-0 -celery==3.1.18 - -# sha256: H1ZavUTEt9-qTdVD1S-YLS8AaroKKzgwVCtNJagB_gk -# sha256: _qhlOuS4en6wmWWUCfCeaANqe4PwoVoLxq7629mKUdw -kombu==3.0.26 +Django==1.8.8 \ + --hash=sha256:05816963cbbadd131bcee8fb5069d8695796e25d081ec24eff62bc1fc8ed891a \ + --hash=sha256:8255242fa0d9e0bf331259a6bdb81364933acbe8863291661558ffdb2fc9ed70 +statsd==3.1 \ + --hash=sha256:788ffa29dfb754900e08b90881f7ea16c3c25ca2949548eea7b43d2ca739e33f \ + --hash=sha256:fbae5feb33ae9394c275bc834ab94684b94de03acc8f36387bd0bf0c51ef28ee +basket-client==0.3.11 \ + --hash=sha256:48f9f69f970988b3df2c8a5684da88b52f115e195eca5b896172db0f8c0260e3 +dj-database-url==0.3.0 \ + --hash=sha256:ca01768fdecde134301f3170743226f60edff5c3935f12437378ebd911506353 \ + --hash=sha256:f2e273ed34acbb560962d5cf12917936d8df02297df09bd3089b8546d4584138 +django-cache-url==1.0.0 \ + --hash=sha256:71506c127aba566ab1b9b77bf09ccd3119a387aeb76df1b34f2541ce4764901c \ + --hash=sha256:3583ca1c9b43b08b9dc71463a5c02fdae6ebebdee812a67198e2a7e429c54210 +django-cors-headers==1.1.0 \ + --hash=sha256:fcd96e2be47c8eef34c650e007a6d546e19e7ee61041b89edbbbbe7619aa3987 +django-jsonfield==0.9.15 \ + --hash=sha256:429690b1c69cbd65c6c592c66d1592f8a1cf3d5282103083731fa90ea53c17c2 +django-mozilla-product-details==0.8.1 \ + --hash=sha256:58dd02c32819ba97c4078b8c133888d30f7fbd3937296ffdad50f79fb57d5cdb +django-picklefield==0.3.1 \ + --hash=sha256:d99426be46fd92fe6b64b9f5c5926ea9859394e44ec3ef9e1f7c647d3a26657f +django-ratelimit==0.6.0 \ + --hash=sha256:f73b53b2c4fd342cbd4b983ffc33fdcf8f8a6f595d548865ea63c839ef38293c \ + --hash=sha256:93dabcd3131a88d6fd19ff4de0a8351ed44b4ec943e8d58d27306f7faf8821af +django-sslify-admin==0.5.0 \ + --hash=sha256:f4f3877e4ace8451ded7764f7f4b70459da405d73a5a72a28f0d23f50080291d +django-statsd-mozilla==0.3.15 \ + --hash=sha256:c0e90f304bbf6f10f63e0019102980c950dc683326d2a4e50e7199decc43c36a +pathlib==1.0.1 \ + --hash=sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f +python-dateutil==1.5 \ + --hash=sha256:6f197348b46fb8cdf9f3fcfc2a7d5a97da95db3e2e8667cf657216274fe1b009 +python-decouple==2.3 \ + --hash=sha256:b9fbf747fa8e711d330f2f436b9b2ecf5eed9eb67a637ca81abcd11c2abedec8 +pytz==2015.4 \ + --hash=sha256:4d64ed1b9e0e73095f5cfa87f0e97ddb4c840049e8efeb7e63b46118ba1d623a \ + --hash=sha256:a78b484d5472dd8c688f8b3eee18646a25c66ce45b2c26652850f6af9ce52b17 +requests==2.7.0 \ + --hash=sha256:20f976cdce02a42b69ce80e9e03897a51814b36d448b37288546086ebc473146 \ + --hash=sha256:398a3db6d61899d25fd4a06c6ca12051b0ce171d705decd7ed5511517b4bb93d +six==1.9.0 \ + --hash=sha256:418a93c397a7edab23e5588dbc067ac74a723edb3d541bd4936f79476e7645da \ + --hash=sha256:e24052411fc4fbd1f672635537c3fc2330d9481b18c0317695b46259512c91d5 +suds==0.4 \ + --hash=sha256:d5524ae523bb9e09c57bcdbd1efae2c287d20603688ea31f6020ed180a489af0 +WebOb==1.4.1 \ + --hash=sha256:12f8b98390befc47336d2c0e5bad9cc48609d808eabb3f8675dc1027a3a9e9db +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 +ua-parser==0.5.1 \ + --hash=sha256:69a4cdaef3268d88f2dc6f8b8f5c3a13955fe3daa5f59746a817dcf891be5899 +https://github.com/selwin/python-user-agents/archive/ed386c818ff7a10849348711d4bdd00506088702.tar.gz#egg=user-agents==1.0.1 \ + --hash=sha256:82be0e44ed40c0eaf87ea0e87a24fc8f0afdd8eb14ffb478aad02cb29e3eed2c +amqp==1.4.6 \ + --hash=sha256:ebcfc867de5a68f9f5ba14d11dbad88e6aff8435a8d39339d5ceb0e5b06de640 \ + --hash=sha256:e34e9765a6120f4664c120f4198a786c39a0fb7e4a59bd19a2d6e3a884b63889 +anyjson==0.3.3 \ + --hash=sha256:37812d863c9ad3e35c0734c42e0bf0320ce8c3bed82cd20ad54cb34d158157ba +billiard==3.3.0.20 \ + --hash=sha256:688f9466b1c3ae14106381e6dbd328115e75c5260c542eb48e6c46931f6928cc +celery==3.1.18 \ + --hash=sha256:0924f94070c6fc57d408b169848c5b38832668fffe060e48b4803fb23e0e3eaf \ + --hash=sha256:dbf59618d5a9eff172d25021f36614be5af0501e4527975ca504b95863f14fed +kombu==3.0.26 \ + --hash=sha256:1f565abd44c4b7dfaa4dd543d52f982d2f006aba0a2b3830542b4d25a801fe09 \ + --hash=sha256:fea8653ae4b87a7eb099659409f09e68036a7b83f0a15a0bc6aefadbd98a51dc diff --git a/requirements/compiled.txt b/requirements/compiled.txt deleted file mode 100644 index d7411cf..0000000 --- a/requirements/compiled.txt +++ /dev/null @@ -1,2 +0,0 @@ -# sha256: H6pJd-mFBzHot_ih1YuGJzk6N0RUN4jsQ3-0XgbP5AE -MySQL-python==1.2.3c1 diff --git a/requirements/dev.txt b/requirements/dev.txt index bfc1c0e..9850575 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,61 +4,45 @@ -r base.txt -# sha256: ShTGfVIP2p1CsNphNGOFeMquHTdLm7Ri2N4AWH26dkw -cov-core==1.15.0 - -# sha256: 0a6hxKphuDZtakLdNlBiL7-cY07STq9_N5yLlw5e1E4 -coverage==3.7.1 - -# sha256: x9txeBCraWX2bIzwOYqYydjfmC2jm0zX8WKRHriVlvo -docutils==0.12 - -# sha256: jc5PfmTMICzG2pPquEss5mARD_aEtnOLumSgpDGzvGk -flake8==2.0 - -# sha256: HMA-8ytkvhngpbVFeN15CQajSUP-kQLP2uDUSVvVNrQ -# sha256: vB_y_4jb-s795N3eRx0UF9OzBOjfEDp6lDfUcmkgG_Q -Jinja2==2.8 - -# sha256: pOwa_1m5WhS0XrLiN2GgF56YMZ2lp-t2tW6ozce4ccM -MarkupSafe==0.23 - -# sha256: vWwID7NyrrywzhnjXdrHRPKr9ae--iB9stEJfUjv5jo -# sha256: X36m-zqpr-FG0H_W1c7feIdH2LDCnkRzJFPCstsePRY -mccabe==0.3.1 - -# sha256: uDndLZwRfHAUMMFJlWkYpCOphjtIsJyQ4wpgE-fS9E8 -mock==1.0.1 - -# sha256: YDpG5cNYziCsSAeg7q-sdQXRElpMG9g3h1etoG9hvtg -pep8==1.4.6 - -# sha256: B-IKuQpVC9PCGJHg2IfwkxtAmPFIrsleKbUYjxYbsHU -# sha256: twPldoXtfCgLGlHElqSYTYPYne8qkwtenl2lpsoVFRQ -py==1.4.30 - -# sha256: Bd9YSinu6poqIRDdNi5T0E4MS7F1S01xI09lGRfzwvA -# sha256: AmkcI85pnyUodLfCfxTPJuPU6CtY5dWE8AC3q1vjal8 -pyflakes==0.9.2 - -# sha256: CjoiZeHvsN76ci0fQpcw3J35da3DxgrH0rQyvfA8BB0 -# sha256: cyCRkITm2sj0VAY4pGRHo71zD8oXKvwX0sA-7SLPT1E -Pygments==2.0.2 - -# sha256: gpJO-16ng6ciNGgqDoBJ2E9erryqw8jAiTt-rpfyg4A -# sha256: swRX9zVCDQAA0QpEu9R4zwP4vyDiW9dySPm6tA9P1qQ -pytest==2.7.2 - -# sha256: MeUZjAbykOHoE9MN3QY6BAHZ46aYEpykQmd4FnOnEoU -pytest-cov==1.8.1 - -# sha256: 12-TTnf6Bz9IzFIZRaSZAKhZ5hD6Ap3YgNHYuZe3fCM -# sha256: 0UWsncelV6cZq3l3C-CUEATh4DjhN8NFkZGdnfKnkLE -pytest-django==2.8.0 - -# sha256: A2vCtitdOpkeRcOJdIzPth6I61sUsnyrS1wfvNT_0ZE -pytest-pythonpath==0.7 - -# sha256: lUX_5P0XsZ-LgVaaZQduwaGXIBjczG1wufI2JlAwx18 -# sha256: LTQV9bPmt1NYd_TIT-IovbgCqJk8I5stAsIxadZzSb0 -Sphinx==1.2.2 +cov-core==1.15.0 \ + --hash=sha256:4a14c67d520fda9d42b0da6134638578caae1d374b9bb462d8de00587dba764c +coverage==3.7.1 \ + --hash=sha256:d1aea1c4aa61b8366d6a42dd3650622fbf9c634ed24eaf7f379c8b970e5ed44e +docutils==0.12 \ + --hash=sha256:c7db717810ab6965f66c8cf0398a98c9d8df982da39b4cd7f162911eb89596fa +flake8==2.0 \ + --hash=sha256:8dce4f7e64cc202cc6da93eab84b2ce660110ff684b6738bba64a0a431b3bc69 +Jinja2==2.8 \ + --hash=sha256:1cc03ef32b64be19e0a5b54578dd790906a34943fe9102cfdae0d4495bd536b4 \ + --hash=sha256:bc1ff2ff88dbfacefde4ddde471d1417d3b304e8df103a7a9437d47269201bf4 +MarkupSafe==0.23 \ + --hash=sha256:a4ec1aff59b95a14b45eb2e23761a0179e98319da5a7eb76b56ea8cdc7b871c3 +mccabe==0.3.1 \ + --hash=sha256:bd6c080fb372aebcb0ce19e35ddac744f2abf5a7befa207db2d1097d48efe63a \ + --hash=sha256:5f7ea6fb3aa9afe146d07fd6d5cedf788747d8b0c29e44732453c2b2db1e3d16 +mock==1.0.1 \ + --hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f +pep8==1.4.6 \ + --hash=sha256:603a46e5c358ce20ac4807a0eeafac7505d1125a4c1bd8378757ada06f61bed8 +py==1.4.30 \ + --hash=sha256:07e20ab90a550bd3c21891e0d887f0931b4098f148aec95e29b5188f161bb075 \ + --hash=sha256:b703e57685ed7c280b1a51c496a4984d83d89def2a930b5e9e5da5a6ca151514 +pyflakes==0.9.2 \ + --hash=sha256:05df584a29eeea9a2a2110dd362e53d04e0c4bb1754b4d71234f651917f3c2f0 \ + --hash=sha256:02691c23ce699f252874b7c27f14cf26e3d4e82b58e5d584f000b7ab5be36a5f +Pygments==2.0.2 \ + --hash=sha256:0a3a2265e1efb0defa722d1f429730dc9df975adc3c60ac7d2b432bdf03c041d \ + --hash=sha256:7320919084e6dac8f4540638a46447a3bd730fca172afc17d2c03eed22cf4f51 +pytest==2.7.2 \ + --hash=sha256:82924efb5ea783a72234682a0e8049d84f5eaebcaac3c8c0893b7eae97f28380 \ + --hash=sha256:b30457f735420d0000d10a44bbd478cf03f8bf20e25bd77248f9bab40f4fd6a4 +pytest-cov==1.8.1 \ + --hash=sha256:31e5198c06f290e1e813d30ddd063a0401d9e3a698129ca44267781673a71285 +pytest-django==2.8.0 \ + --hash=sha256:d76f934e77fa073f48cc521945a49900a859e610fa029dd880d1d8b997b77c23 \ + --hash=sha256:d145ac9dc7a557a719ab79770be0941004e1e038e137c34591919d9df2a790b1 +pytest-pythonpath==0.7 \ + --hash=sha256:036bc2b62b5d3a991e45c389748ccfb61e88eb5b14b27cab4b5c1fbcd4ffd191 +Sphinx==1.2.2 \ + --hash=sha256:9545ffe4fd17b19f8b81569a65076ec1a1972018dccc6d70b9f236265030c75f \ + --hash=sha256:2d3415f5b3e6b7535877f4c84fe228bdb802a8993c239b2d02c23169d67349bd diff --git a/requirements/pip.txt b/requirements/pip.txt deleted file mode 100644 index 00cb5f1..0000000 --- a/requirements/pip.txt +++ /dev/null @@ -1,3 +0,0 @@ -# sha256: pn5Uqg8mttYszsXMZzXv8gXdD-0HX1asPTER6R5EZ_w -# sha256: ifO2JtIl4I5_INhQRK-kD2EusyhEhBaYE9wtBjHypVY -pip==6.1.1 diff --git a/requirements/prod.txt b/requirements/prod.txt index 363909a..287d66e 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,20 +1,15 @@ -r base.txt -# sha256: mtaymUWPfmv676iQX1JWABc2nYL7j7DtS0GtwEjb8Rw -django-redis==4.2.0 - -# sha256: pRec3pItK04EXuWxHocyPud9NjrkApT8glLyXWoOrwY -gunicorn==19.3.0 - -# sha256: pPs3sChg9rFhf2RpSHRx_Qht0tOLvOZAwgVYYrnEAZw -redis==2.10.3 - -# sha256: ypWOExKOSWdKpKlvAnRvXeWXPzm1cpe4TVn9RNMU1bU -hiredis==0.2.0 - -# sha256: v8xYHJ278HzC-VG68wwySaV-INy9YPfm_8Q6s8xhR5Q -msgpack-python==0.4.6 - -# sha256: GclSD80sHoOWHNx92wsN63uToa6nn1buDWxeJAP8qk0 -# sha256: wLZGoH80oy6PZjJqgZzu8PvBwjNW3IRFFgvHaGGw30w -whitenoise==2.0.3 +django-redis==4.2.0 \ + --hash=sha256:9ad6b299458f7e6bfaefa8905f52560017369d82fb8fb0ed4b41adc048dbf11c +gunicorn==19.3.0 \ + --hash=sha256:a5179cde922d2b4e045ee5b11e87323ee77d363ae40294fc8252f25d6a0eaf06 +redis==2.10.3 \ + --hash=sha256:a4fb37b02860f6b1617f6469487471fd086dd2d38bbce640c2055862b9c4019c +hiredis==0.2.0 \ + --hash=sha256:ca958e13128e49674aa4a96f02746f5de5973f39b57297b84d59fd44d314d5b5 +msgpack-python==0.4.6 \ + --hash=sha256:bfcc581c9dbbf07cc2f951baf30c3249a57e20dcbd60f7e6ffc43ab3cc614794 +whitenoise==2.0.3 \ + --hash=sha256:19c9520fcd2c1e83961cdc7ddb0b0deb7b93a1aea79f56ee0d6c5e2403fcaa4d \ + --hash=sha256:c0b646a07f34a32e8f66326a819ceef0fbc1c23356dc8445160bc76861b0df4c diff --git a/requirements/travis.txt b/requirements/travis.txt new file mode 100644 index 0000000..f21ab8e --- /dev/null +++ b/requirements/travis.txt @@ -0,0 +1 @@ +MySQL-python==1.2.3c1 --hash=sha256:1faa4977e9850731e8b7f8a1d58b8627393a3744543788ec437fb45e06cfe401