Skip to content

Enable Warehouse to run on Python 3.3+, Python 2.7, and PyPy #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 3, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ omit =
# Migrations don't make sense to include in coverage
warehouse/migrations/versions/*
warehouse/migrations/env.py

# The compat module contains things that are only run on one Python version
warehouse/compat.py
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: python
python: 2.7
python: 3.3

addons:
postgresql: 9.3
Expand All @@ -9,8 +9,10 @@ env:
- WAREHOUSE_DATABASE_URL=postgresql://postgres@localhost/warehouse
matrix:
- TOXENV=py27
- TOXENV=py33
- TOXENV=pypy
- TOXENV=pep8
- TOXENV=py2pep8
- TOXENV=docs
- TOXENV=packaging

Expand Down
21 changes: 21 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,24 @@ universal = 1
ignore =
.travis.yml
tasks.py

[metadata]
requires-dist =
alembic
arrow
babel
elasticsearch
enum34
guard
Jinja2
PyYAML
raven
readme>=0.1.1
redis
six
SQLAlchemy
sqlalchemy-citext>=1.2.0
Werkzeug

psycopg2; platform_python_implementation == 'CPython'
psycopg2cffi; platform_python_implementation == 'PyPy'
43 changes: 26 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import fnmatch
import os
import platform

from setuptools import setup, find_packages

Expand All @@ -34,6 +35,30 @@ def recursive_glob(path, pattern, cutdirs=0):
matches.append(filepath)
return matches

# Note: Adjusting these requires adjusting setup.cfg as well
install_requires = [
"alembic",
"arrow",
"babel",
"elasticsearch",
"enum34",
"guard",
"Jinja2",
"PyYAML",
"raven",
"readme>=0.1.1",
"redis",
"six",
"SQLAlchemy",
"sqlalchemy-citext>=1.2.0",
"Werkzeug",
]

if platform.python_implementation() == "PyPy":
install_requires += ["psycopg2cffi"]
else:
install_requires += ["psycopg2"]


setup(
name=about["__title__"],
Expand Down Expand Up @@ -64,23 +89,7 @@ def recursive_glob(path, pattern, cutdirs=0):
"warehouse.migrations": ["*.mako", "versions/*.py"],
},

install_requires=[
"alembic",
"arrow",
"babel",
"elasticsearch",
"enum34",
"guard",
"Jinja2",
"psycopg2cffi",
"PyYAML",
"raven",
"readme>=0.1.1",
"redis",
"SQLAlchemy",
"sqlalchemy-citext>=1.2.0",
"Werkzeug",
],
install_requires=install_requires,

entry_points={
"console_scripts": [
Expand Down
8 changes: 4 additions & 4 deletions tests/legacy/test_pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def test_daytime(monkeypatch):

resp = pypi.daytime(app, request)

assert resp.response[0] == '19700101T00:00:00\n'
assert resp.response[0] == b'19700101T00:00:00\n'


@pytest.mark.parametrize("callback", [None, 'yes'])
Expand Down Expand Up @@ -130,7 +130,7 @@ def test_json(monkeypatch, callback):
'"upload_time": "1970-01-01T00:00:00"}]}'
if callback:
expected = '/**/ %s(%s);' % (callback, expected)
assert resp.data == expected
assert resp.data == expected.encode("utf8")


def test_jsonp_invalid():
Expand Down Expand Up @@ -208,7 +208,7 @@ def test_rss(monkeypatch):
'summary': u'hai spam v2',
'created': u'now',
}]
assert resp.data == "<xml>dummy</xml>"
assert resp.data == b"<xml>dummy</xml>"


def test_packages_rss(monkeypatch):
Expand Down Expand Up @@ -261,7 +261,7 @@ def test_packages_rss(monkeypatch):
'summary': u'hai eggs!',
'created': u'now',
}]
assert resp.data == "<xml>dummy</xml>"
assert resp.data == b"<xml>dummy</xml>"


def test_rss_xml_template(monkeypatch):
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy/test_xmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_xmlrpc_handler(monkeypatch):
assert interface.list_packages.calls == [pretend.call()]

response_xml = Response.calls[0].args[0]
assert response_xml == '''<?xml version='1.0'?>
assert response_xml == b'''<?xml version='1.0'?>
<methodResponse>
<params>
<param>
Expand Down
2 changes: 1 addition & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{},
("https://secure.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e"
"?size=80"),
)
),
])
def test_gravatar_url(email, kwargs, expected):
assert gravatar_url(email, **kwargs) == expected
Expand Down
8 changes: 7 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,pypy,pep8,docs,packaging
envlist = py27,py33,pypy,py2pep8,pep8,docs,packaging

[testenv]
deps =
Expand All @@ -26,7 +26,13 @@ deps = check-manifest
commands =
check-manifest

[testenv:py2pep8]
basepython=python2.7
deps = flake8
commands = flake8 .

[testenv:pep8]
basepython=python3.3
deps = flake8
commands = flake8 .

Expand Down
6 changes: 2 additions & 4 deletions warehouse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals

import psycopg2cffi.compat

from warehouse.__about__ import (
__title__, __summary__, __uri__, __version__, __author__, __email__,
__license__, __copyright__, __build__,
)
from warehouse.compat import psycopg2_register

__all__ = [
"__title__", "__summary__", "__uri__", "__version__", "__author__",
"__email__", "__license__", "__copyright__", "__build__",
]


psycopg2cffi.compat.register()
psycopg2_register()
30 changes: 30 additions & 0 deletions warehouse/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2013 Donald Stufft
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals

# flake8: noqa

import platform

try:
from xmlrpc.server import SimpleXMLRPCDispatcher
except ImportError:
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher


def psycopg2_register():
if platform.python_implementation() == "PyPy":
import psycopg2cffi.compat
psycopg2cffi.compat.register()
10 changes: 5 additions & 5 deletions warehouse/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import hashlib
import json
import os.path
import urllib
import urlparse

from six.moves import urllib_parse

import warehouse

Expand All @@ -35,14 +35,14 @@ def gravatar_url(email, size=80):
if email is None:
email = ""

email_hash = hashlib.md5(email.strip().lower()).hexdigest()
email_hash = hashlib.md5(email.strip().lower().encode("utf8")).hexdigest()

url = "https://secure.gravatar.com/avatar/{}".format(email_hash)
params = {
"size": size,
}

return "?".join([url, urllib.urlencode(params)])
return "?".join([url, urllib_parse.urlencode(params)])


def static_url(app, filename):
Expand Down Expand Up @@ -70,4 +70,4 @@ def static_url(app, filename):
if basename is not None:
filename = os.path.join(os.path.dirname(filename), basename)

return urlparse.urljoin("/static/", filename)
return urllib_parse.urljoin("/static/", filename)
3 changes: 1 addition & 2 deletions warehouse/legacy/xmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals

from SimpleXMLRPCServer import SimpleXMLRPCDispatcher

import arrow
from werkzeug.exceptions import BadRequest

from warehouse.compat import SimpleXMLRPCDispatcher
from warehouse.http import Response


Expand Down
9 changes: 5 additions & 4 deletions warehouse/packaging/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

import datetime
import os.path
import urlparse
import logging

from six.moves import urllib_parse

from warehouse import db
from warehouse.packaging.tables import ReleaseDependencyKind

Expand Down Expand Up @@ -199,7 +200,7 @@ def get_top_projects(self, num=None):
""",
lambda r: {
"filename": r["filename"],
"url": urlparse.urljoin(
"url": urllib_parse.urljoin(
"/".join([
"../../packages",
r["python_version"],
Expand Down Expand Up @@ -477,7 +478,7 @@ def search_by_classifier(self, selected_classifiers):
releases = []
with self.engine.connect() as conn:
for name, version in conn.execute(query):
releases.append((name.decode('utf-8'), version))
releases.append((name, version))

return releases

Expand All @@ -488,7 +489,7 @@ def get_documentation_url(self, project):
"index.html",
]
if os.path.exists(os.path.join(*path_parts)):
return urlparse.urljoin(
return urllib_parse.urljoin(
self.app.config.urls.documentation,
project
) + "/"
Expand Down
4 changes: 2 additions & 2 deletions warehouse/search/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def reindex(self, alias=True, keep_old=False):
# Generate an Index Name for Warehouse
index = "".join([
self._index,
binascii.hexlify(os.urandom(4)),
binascii.hexlify(os.urandom(4)).decode("ascii"),
])

# Create this index
Expand All @@ -67,7 +67,7 @@ def reindex(self, alias=True, keep_old=False):
def update_alias(self, alias, index, keep_old=False):
# Get the old index from ElasticSearch
try:
old_index = self.es.indices.get_alias(self._index).keys()[0]
old_index = list(self.es.indices.get_alias(self._index))[0]
except TransportError as exc:
if not exc.status_code == 404:
raise
Expand Down