Source

onpython3yet / onpython3yet / packages / views.py

Full commit
import logging
import os
import pprint
import sys
import xmlrpclib

from django.db import transaction
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response, get_object_or_404
from django.template import loader, RequestContext
import simplejson

from packages.models import Package, PackageRequirement, MostRequiredPackages
from packages import requirelib
from onpython3yet.utils import as_json

log = logging.getLogger()


class PackageNotFound(LookupError):
    """The package was not found on PyPI."""


@transaction.commit_on_success()
def fetch(request, package_name):
    pkg = _fetch_package(package_name)
    return HttpResponseRedirect(reverse('packages.show',
                                args=(pkg.name,)))


def _fetch_package(name):
    server = xmlrpclib.Server('http://pypi.python.org/pypi')
    pypi_package = _get_pypi_package_by_name(server, name)
    return _sync_package_with_pypi(server, pypi_package)


def _get_pypi_package_by_name(server, package_name):
    result = server.search({'name': package_name})
    pypi_package = None
    for pkg in result:
        if pkg['name'].lower() == package_name.lower():
            pypi_package = pkg
            break
    if not pypi_package:
        raise PackageNotFound("No exact match for %r" % package_name)

    return pypi_package


def _sync_package_with_pypi(server, pypi_package):
    current_version = pypi_package['version']
    log.info("Creating: %r" % pypi_package)
    release_data = server.release_data(pypi_package['name'], current_version)

    supports_python_3 = False
    if release_data.get('classifiers'):
        for cls in release_data['classifiers']:
            # supports 3.x
            if cls.startswith('Programming Language :: Python :: 3'):
                supports_python_3 = True

    pkg_data = dict(
        name=pypi_package['name'],
        current_version=pypi_package['version'],
        summary=pypi_package.get('summary', None),
        description=pypi_package.get('description', None),
        supports_python_3=supports_python_3
    )
    db_package = Package.objects.filter(name=pypi_package['name'])
    if not db_package.count():
        db_package = Package(**pkg_data)
    else:
        db_package.update(**pkg_data)
        # query set -> object
        db_package = Package.objects.get(name=pypi_package['name'])
        # prepare to refresh requirements (below)
        for r in db_package.requirements.all():
            r.delete()

    for required_pkg_name in release_data.get('requires', []):
        if required_pkg_name == db_package.name:
            # a package cannot require itself
            # however it is possible to declare this.
            continue
        required_pkg_name = requirelib.strip_spec(required_pkg_name)
        try:
            query = Package.objects.filter(name=required_pkg_name)
            if query.count():
                required_package = query[0]
            else:
                r_pypi_package = _get_pypi_package_by_name(server,
                                                           required_pkg_name)
                if r_pypi_package:
                    log.info("Creating requirement of %r: %r" % (
                                db_package.name, r_pypi_package['name']))
                    required_package = _sync_package_with_pypi(
                                                    server, r_pypi_package)
                else:
                    raise PackageNotFound("No results for %r" %
                                          required_pkg_name)
            requirement = PackageRequirement(
                package=db_package,
                required_package=required_package
            )
            db_package.requirements.add(requirement)
        except PackageNotFound, exc:
            log.warning("Could not find requirement %r for package %r" % (
                                    required_pkg_name, db_package.name))
            # if a requirement cannot be found just ignore the requirement
            pass

    db_package.save()
    return db_package


@as_json
@transaction.commit_on_success()
def query(request):
    package_name = request.POST['name']
    query = Package.objects.filter(name=package_name)
    pkg = None
    result = {
        'name': package_name,
    }
    if query.count():
        pkg = query[0]
        result['found_pkg'] = True
    else:
        try:
            pkg = _fetch_package(package_name)
        except PackageNotFound:
            result.update({
                'found_pkg': False,
                'not_found_reason': 'Does not exist on PyPI'
            })
    if pkg:
        result.update({
            'name': pkg.name, # cannonical case
            'supports_python_3': pkg.supports_python_3
        })
    return result


def _json_package(pkg):
    return {
        'name': pkg.name,
        'show_pkg_url': reverse('packages.show', args=[pkg.name]),
        'required_count': pkg.required_count
    }


@as_json
def depended_on(request):
    default_start = 0
    default_offset = 20
    try:
        start = int(request.GET.get('start', default_start))
        offset = int(request.GET.get('offset', default_offset))
    except ValueError:
        start = default_start
        offset = default_offset
    qs = MostRequiredPackages.objects.all()
    return {
        'packages': [_json_package(p) for p in qs[start:start+offset]]
    }


def show(request, package_name):
    query = Package.objects.filter(name=package_name)
    if query.count():
        pkg = query[0]
    else:
        # TODO(Kumar) I think this redirect will go away once a proper query
        # page is set up
        return HttpResponseRedirect(reverse('packages.fetch',
                                    args=(package_name,)))
    return render_to_response('packages/show.html', {'package': pkg},
            context_instance=RequestContext(request))


def index(request):
    return HttpResponseRedirect(reverse('home.index'))


def _normalize_requirements(req):
    r = []
    for line in req.split("\n"):
        line = line.strip()
        if not line:
            continue
        # TODO(Kumar) identify -e / git+ / svn+ / hg+ URLs
        for spec in line.split(' '):
            r.append(spec)
    return r


def _serialize_requirements(r):
    # Hmm. Chose this format so as not to worry about XSS, etc
    return ';;;'.join(r)


def requirements(request):
    requirements_input = request.GET.get('r', u'')
    requirements = _serialize_requirements(_normalize_requirements(
                                           requirements_input))
    return render_to_response('packages/requirements.html',
                              {'requirements': requirements,
                               'requirements_input': requirements_input},
                              context_instance=RequestContext(request))