Skip to content

Commit 760d286

Browse files
committed
Merge pull request #218 from dstufft/python3
Enable Warehouse to run on Python 3.3+, Python 2.7, and PyPy
2 parents 38f5ec2 + facded5 commit 760d286

File tree

14 files changed

+111
-42
lines changed

14 files changed

+111
-42
lines changed

.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ omit =
2424
# Migrations don't make sense to include in coverage
2525
warehouse/migrations/versions/*
2626
warehouse/migrations/env.py
27+
28+
# The compat module contains things that are only run on one Python version
29+
warehouse/compat.py

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: python
2-
python: 2.7
2+
python: 3.3
33

44
addons:
55
postgresql: 9.3
@@ -9,8 +9,10 @@ env:
99
- WAREHOUSE_DATABASE_URL=postgresql://postgres@localhost/warehouse
1010
matrix:
1111
- TOXENV=py27
12+
- TOXENV=py33
1213
- TOXENV=pypy
1314
- TOXENV=pep8
15+
- TOXENV=py2pep8
1416
- TOXENV=docs
1517
- TOXENV=packaging
1618

setup.cfg

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,24 @@ universal = 1
55
ignore =
66
.travis.yml
77
tasks.py
8+
9+
[metadata]
10+
requires-dist =
11+
alembic
12+
arrow
13+
babel
14+
elasticsearch
15+
enum34
16+
guard
17+
Jinja2
18+
PyYAML
19+
raven
20+
readme>=0.1.1
21+
redis
22+
six
23+
SQLAlchemy
24+
sqlalchemy-citext>=1.2.0
25+
Werkzeug
26+
27+
psycopg2; platform_python_implementation == 'CPython'
28+
psycopg2cffi; platform_python_implementation == 'PyPy'

setup.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import fnmatch
1818
import os
19+
import platform
1920

2021
from setuptools import setup, find_packages
2122

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

38+
# Note: Adjusting these requires adjusting setup.cfg as well
39+
install_requires = [
40+
"alembic",
41+
"arrow",
42+
"babel",
43+
"elasticsearch",
44+
"enum34",
45+
"guard",
46+
"Jinja2",
47+
"PyYAML",
48+
"raven",
49+
"readme>=0.1.1",
50+
"redis",
51+
"six",
52+
"SQLAlchemy",
53+
"sqlalchemy-citext>=1.2.0",
54+
"Werkzeug",
55+
]
56+
57+
if platform.python_implementation() == "PyPy":
58+
install_requires += ["psycopg2cffi"]
59+
else:
60+
install_requires += ["psycopg2"]
61+
3762

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

67-
install_requires=[
68-
"alembic",
69-
"arrow",
70-
"babel",
71-
"elasticsearch",
72-
"enum34",
73-
"guard",
74-
"Jinja2",
75-
"psycopg2cffi",
76-
"PyYAML",
77-
"raven",
78-
"readme>=0.1.1",
79-
"redis",
80-
"SQLAlchemy",
81-
"sqlalchemy-citext>=1.2.0",
82-
"Werkzeug",
83-
],
92+
install_requires=install_requires,
8493

8594
entry_points={
8695
"console_scripts": [

tests/legacy/test_pypi.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_daytime(monkeypatch):
8888

8989
resp = pypi.daytime(app, request)
9090

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

9393

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

135135

136136
def test_jsonp_invalid():
@@ -208,7 +208,7 @@ def test_rss(monkeypatch):
208208
'summary': u'hai spam v2',
209209
'created': u'now',
210210
}]
211-
assert resp.data == "<xml>dummy</xml>"
211+
assert resp.data == b"<xml>dummy</xml>"
212212

213213

214214
def test_packages_rss(monkeypatch):
@@ -261,7 +261,7 @@ def test_packages_rss(monkeypatch):
261261
'summary': u'hai eggs!',
262262
'created': u'now',
263263
}]
264-
assert resp.data == "<xml>dummy</xml>"
264+
assert resp.data == b"<xml>dummy</xml>"
265265

266266

267267
def test_rss_xml_template(monkeypatch):

tests/legacy/test_xmlrpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_xmlrpc_handler(monkeypatch):
5656
assert interface.list_packages.calls == [pretend.call()]
5757

5858
response_xml = Response.calls[0].args[0]
59-
assert response_xml == '''<?xml version='1.0'?>
59+
assert response_xml == b'''<?xml version='1.0'?>
6060
<methodResponse>
6161
<params>
6262
<param>

tests/test_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
{},
4141
("https://secure.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e"
4242
"?size=80"),
43-
)
43+
),
4444
])
4545
def test_gravatar_url(email, kwargs, expected):
4646
assert gravatar_url(email, **kwargs) == expected

tox.ini

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27,pypy,pep8,docs,packaging
2+
envlist = py27,py33,pypy,py2pep8,pep8,docs,packaging
33

44
[testenv]
55
deps =
@@ -26,7 +26,13 @@ deps = check-manifest
2626
commands =
2727
check-manifest
2828

29+
[testenv:py2pep8]
30+
basepython=python2.7
31+
deps = flake8
32+
commands = flake8 .
33+
2934
[testenv:pep8]
35+
basepython=python3.3
3036
deps = flake8
3137
commands = flake8 .
3238

warehouse/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@
1414
from __future__ import absolute_import, division, print_function
1515
from __future__ import unicode_literals
1616

17-
import psycopg2cffi.compat
18-
1917
from warehouse.__about__ import (
2018
__title__, __summary__, __uri__, __version__, __author__, __email__,
2119
__license__, __copyright__, __build__,
2220
)
21+
from warehouse.compat import psycopg2_register
2322

2423
__all__ = [
2524
"__title__", "__summary__", "__uri__", "__version__", "__author__",
2625
"__email__", "__license__", "__copyright__", "__build__",
2726
]
2827

29-
30-
psycopg2cffi.compat.register()
28+
psycopg2_register()

warehouse/compat.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2013 Donald Stufft
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import absolute_import, division, print_function
15+
from __future__ import unicode_literals
16+
17+
# flake8: noqa
18+
19+
import platform
20+
21+
try:
22+
from xmlrpc.server import SimpleXMLRPCDispatcher
23+
except ImportError:
24+
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
25+
26+
27+
def psycopg2_register():
28+
if platform.python_implementation() == "PyPy":
29+
import psycopg2cffi.compat
30+
psycopg2cffi.compat.register()

warehouse/helpers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import hashlib
1818
import json
1919
import os.path
20-
import urllib
21-
import urlparse
20+
21+
from six.moves import urllib_parse
2222

2323
import warehouse
2424

@@ -35,14 +35,14 @@ def gravatar_url(email, size=80):
3535
if email is None:
3636
email = ""
3737

38-
email_hash = hashlib.md5(email.strip().lower()).hexdigest()
38+
email_hash = hashlib.md5(email.strip().lower().encode("utf8")).hexdigest()
3939

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

45-
return "?".join([url, urllib.urlencode(params)])
45+
return "?".join([url, urllib_parse.urlencode(params)])
4646

4747

4848
def static_url(app, filename):
@@ -70,4 +70,4 @@ def static_url(app, filename):
7070
if basename is not None:
7171
filename = os.path.join(os.path.dirname(filename), basename)
7272

73-
return urlparse.urljoin("/static/", filename)
73+
return urllib_parse.urljoin("/static/", filename)

warehouse/legacy/xmlrpc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
from __future__ import absolute_import, division, print_function
1515
from __future__ import unicode_literals
1616

17-
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
18-
1917
import arrow
2018
from werkzeug.exceptions import BadRequest
2119

20+
from warehouse.compat import SimpleXMLRPCDispatcher
2221
from warehouse.http import Response
2322

2423

warehouse/packaging/db.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
import datetime
1818
import os.path
19-
import urlparse
2019
import logging
2120

21+
from six.moves import urllib_parse
22+
2223
from warehouse import db
2324
from warehouse.packaging.tables import ReleaseDependencyKind
2425

@@ -199,7 +200,7 @@ def get_top_projects(self, num=None):
199200
""",
200201
lambda r: {
201202
"filename": r["filename"],
202-
"url": urlparse.urljoin(
203+
"url": urllib_parse.urljoin(
203204
"/".join([
204205
"../../packages",
205206
r["python_version"],
@@ -477,7 +478,7 @@ def search_by_classifier(self, selected_classifiers):
477478
releases = []
478479
with self.engine.connect() as conn:
479480
for name, version in conn.execute(query):
480-
releases.append((name.decode('utf-8'), version))
481+
releases.append((name, version))
481482

482483
return releases
483484

@@ -488,7 +489,7 @@ def get_documentation_url(self, project):
488489
"index.html",
489490
]
490491
if os.path.exists(os.path.join(*path_parts)):
491-
return urlparse.urljoin(
492+
return urllib_parse.urljoin(
492493
self.app.config.urls.documentation,
493494
project
494495
) + "/"

warehouse/search/indexes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def reindex(self, alias=True, keep_old=False):
4545
# Generate an Index Name for Warehouse
4646
index = "".join([
4747
self._index,
48-
binascii.hexlify(os.urandom(4)),
48+
binascii.hexlify(os.urandom(4)).decode("ascii"),
4949
])
5050

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

0 commit comments

Comments
 (0)