From 4008e7ba257152ab9b456d14fa23c1a8b76e5695 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 1 Nov 2013 15:15:55 +1100 Subject: [PATCH 01/14] implement xmlrpc list_packages_with_serial --- tests/legacy/test_xmlrpc.py | 45 ++++++++++++++++++++++++---------- tests/packaging/test_models.py | 12 +++++++++ warehouse/legacy/xmlrpc.py | 3 +++ warehouse/packaging/models.py | 10 ++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index da314dcfd45f..201e2c6e99c0 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -66,6 +66,20 @@ def test_xmlrpc_handler(monkeypatch): assert Response.calls[0].kwargs == dict(mimetype='text/xml') +def test_xmlrpc_handler_size_limit(monkeypatch): + app = pretend.stub() + + request = pretend.stub( + headers={ + 'Content-Type': 'text/xml', + 'Content-Length': str(10 * 1024 * 1024 + 1) + }, + ) + + with pytest.raises(BadRequest): + xmlrpc.handle_request(app, request) + + def test_xmlrpc_list_packages(): all_projects = [Project("bar"), Project("foo")] @@ -76,11 +90,8 @@ def test_xmlrpc_list_packages(): ), ), ) - request = pretend.stub( - headers={'Content-Type': 'text/xml'} - ) - interface = xmlrpc.Interface(app, request) + interface = xmlrpc.Interface(app, pretend.stub()) result = interface.list_packages() @@ -88,15 +99,23 @@ def test_xmlrpc_list_packages(): assert result == ['bar', 'foo'] -def test_xmlrpc_size(monkeypatch): - app = pretend.stub() +def test_xmlrpc_list_packages_with_serial(): + serials = dict(one=1, two=2, three=3) - request = pretend.stub( - headers={ - 'Content-Type': 'text/xml', - 'Content-Length': str(10 * 1024 * 1024 + 1) - }, + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_packages_with_serial=pretend.call_recorder(lambda: + serials), + ), + ), ) - with pytest.raises(BadRequest): - xmlrpc.handle_request(app, request) + interface = xmlrpc.Interface(app, pretend.stub()) + + result = interface.list_packages_with_serial() + + assert app.models.packaging.get_packages_with_serial.calls == [ + pretend.call(), + ] + assert result == dict(one=1, two=2, three=3) diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index f482fea03abe..834c8c21b072 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -456,6 +456,18 @@ def test_get_last_serial(name, serial, dbapp): assert dbapp.models.packaging.get_last_serial(name) == serial +def test_get_packages_with_serial(dbapp): + dbapp.engine.execute(journals.insert().values(id=1, name='one')) + dbapp.engine.execute(journals.insert().values(id=2, name='two')) + dbapp.engine.execute(journals.insert().values(id=3, name='three')) + + assert dbapp.models.packaging.get_packages_with_serial() == dict( + one=1, + two=2, + three=3 + ) + + def test_get_project_versions(dbapp): dbapp.engine.execute(packages.insert().values(name="test-project")) dbapp.engine.execute(releases.insert().values( diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index e0f3a97641f7..ce9c64a88d57 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -54,3 +54,6 @@ def list_packages(self): ''' projects = self.app.models.packaging.all_projects() return [project.name for project in projects] + + def list_packages_with_serial(self): + return self.app.models.packaging.get_packages_with_serial() diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index c59aff3ec27f..a85cad09f249 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -247,6 +247,16 @@ def get_last_serial(self, name=None): with self.engine.connect() as conn: return conn.execute(query).scalar() + def get_packages_with_serial(self): + # return list of dict(name: max id) + query = ( + select([journals.c.name, func.max(journals.c.id)]) + .group_by(journals.c.name) + ) + + with self.engine.connect() as conn: + return dict(r for r in conn.execute(query)) + def get_project_versions(self, project): query = ( select([releases.c.version]) From e545fca4170d272c224e76639aa6ef0473a0c65b Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 1 Nov 2013 15:45:02 +1100 Subject: [PATCH 02/14] add top_packages xml-rpc; also try to keep doc up to date --- docs/api-reference/index.rst | 1 + docs/api-reference/xml-rpc.rst | 158 +++++++++++++++++++++++++++++++++ tests/legacy/test_xmlrpc.py | 39 ++++++-- tests/packaging/test_models.py | 26 +++++- warehouse/legacy/xmlrpc.py | 10 +-- warehouse/packaging/models.py | 14 ++- 6 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 docs/api-reference/xml-rpc.rst diff --git a/docs/api-reference/index.rst b/docs/api-reference/index.rst index ebc906d28dbd..aecca6bb3e31 100644 --- a/docs/api-reference/index.rst +++ b/docs/api-reference/index.rst @@ -4,6 +4,7 @@ API Reference .. toctree:: :maxdepth: 1 + xml-rpc legacy Application Structure diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst new file mode 100644 index 000000000000..ec6ffe9ed57c --- /dev/null +++ b/docs/api-reference/xml-rpc.rst @@ -0,0 +1,158 @@ + +PyPI's XML-RPC methods +====================== + +Example usage: + + >>> import xmlrpclib + >>> import pprint + >>> client = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') + >>> client.package_releases('roundup') + ['1.4.10'] + >>> pprint.pprint(client.release_urls('roundup', '1.4.10')) + [{'comment_text': '', + 'downloads': 3163, + 'filename': 'roundup-1.1.2.tar.gz', + 'has_sig': True, + 'md5_digest': '7c395da56412e263d7600fa7f0afa2e5', + 'packagetype': 'sdist', + 'python_version': 'source', + 'size': 876455, + 'upload_time': , + 'url': 'http://pypi.python.org/packages/source/r/roundup/roundup-1.1.2.tar.gz'}, + {'comment_text': '', + 'downloads': 2067, + 'filename': 'roundup-1.1.2.win32.exe', + 'has_sig': True, + 'md5_digest': '983d565b0b87f83f1b6460e54554a845', + 'packagetype': 'bdist_wininst', + 'python_version': 'any', + 'size': 614270, + 'upload_time': , + 'url': 'http://pypi.python.org/packages/any/r/roundup/roundup-1.1.2.win32.exe'}] + +Package Querying +---------------- + +``list_packages()`` + Retrieve a list of the package names registered with the package index. + Returns a list of name strings. + +``package_releases(package_name, show_hidden=False)`` + Retrieve a list of the releases registered for the given package_name. + Returns a list with all version strings if show_hidden is True or only the + non-hidden ones otherwise. + +``package_roles(package_name)`` + Retrieve a list of users and their attributes roles for a given package_name. + Role is either 'Maintainer' or 'Owner'. + +``user_packages(user)`` + Retrieve a list of [role_name, package_name] for a given username. + Role is either 'Maintainer' or 'Owner'. + +``release_downloads(package_name, version)`` + Retrieve a list of files and download count for a given package and release + version. + +``release_urls(package_name, version)`` + Retrieve a list of download URLs for the given package release. + Returns a list of dicts with the following keys: + - url + - packagetype ('sdist', 'bdist', etc) + - filename + - size + - md5_digest + - downloads + - has_sig + - python_version (required version, or 'source', or 'any') + - comment_text + +``release_data(package_name, version)`` + Retrieve metadata describing a specific package release. + Returns a dict with keys for: + - name + - version + - stable_version + - author + - author_email + - maintainer + - maintainer_email + - home_page + - license + - summary + - description + - keywords + - platform + - download_url + - classifiers (list of classifier strings) + - requires + - requires_dist + - provides + - provides_dist + - requires_external + - requires_python + - obsoletes + - obsoletes_dist + - project_url + - docs_url (URL of the packages.python.org docs if they've been supplied) + If the release does not exist, an empty dictionary is returned. + +``search(spec[, operator])`` + Search the package database using the indicated search spec. + + The spec may include any of the keywords described in the above list (except + 'stable_version' and 'classifiers'), for example: {'description': 'spam'} + will search description fields. Within the spec, a field's value can be a + string or a list of strings (the values within the list are combined with an + OR), for example: {'name': ['foo', 'bar']}. Valid keys for the spec dict are + listed here. Invalid keys are ignored: + - name + - version + - author + - author_email + - maintainer + - maintainer_email + - home_page + - license + - summary + - description + - keywords + - platform + - download_url + + Arguments for different fields are combined using either "and" (the default) + or "or". Example: search({'name': 'foo', 'description': 'bar'}, 'or'). The + results are returned as a list of dicts {'name': package name, 'version': + package release version, 'summary': package release summary} + +``browse(classifiers)`` + Retrieve a list of (name, version) pairs of all releases classified with all + of the given classifiers. 'classifiers' must be a list of Trove classifier + strings. + +``top_packages([number])`` + Retrieve the sorted list of packages ranked by number of downloads. + Optionally limit the list to the number given. + + +Mirroring Support +----------------- + +``changelog(since, with_ids=False)`` + Retrieve a list of four-tuples (name, version, timestamp, action), or + five-tuple including the serial id if ids are requested, since the given + timestamp. All timestamps are UTC values. The argument is a UTC integer + seconds since the epoch. + +``changelog_last_serial()`` + Retrieve the last event's serial id. + +``changelog_since_serial(since_serial)`` + Retrieve a list of five-tuples (name, version, timestamp, action, serial) + since the event identified by the given serial. All timestamps are UTC + values. The argument is a UTC integer seconds since the epoch. + +``list_packages_with_serial()`` + Retrieve a dictionary mapping package names to the last serial for each + package. diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 201e2c6e99c0..923577bc94d6 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -99,14 +99,41 @@ def test_xmlrpc_list_packages(): assert result == ['bar', 'foo'] -def test_xmlrpc_list_packages_with_serial(): - serials = dict(one=1, two=2, three=3) +@pytest.mark.parametrize(("num", "result"), [ + (None, [('three', 10000), ('one', 1110), ('two', 22)]), + (2, [('three', 10000), ('one', 1110)]), +]) +def test_xmlrpc_top_packages(num, result): + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_top_projects=pretend.call_recorder(lambda *a: result), + ), + ), + ) + interface = xmlrpc.Interface(app, pretend.stub()) + + if num: + r = interface.top_packages(num) + assert app.models.packaging.get_top_projects.calls == [ + pretend.call(num) + ] + else: + r = interface.top_packages() + assert app.models.packaging.get_top_projects.calls == [ + pretend.call(None) + ] + + assert r == result + + +def test_xmlrpc_list_packages_with_serial(): + d = dict(one=1, two=2, three=3) app = pretend.stub( models=pretend.stub( packaging=pretend.stub( - get_packages_with_serial=pretend.call_recorder(lambda: - serials), + get_projects_with_serial=pretend.call_recorder(lambda: d), ), ), ) @@ -115,7 +142,7 @@ def test_xmlrpc_list_packages_with_serial(): result = interface.list_packages_with_serial() - assert app.models.packaging.get_packages_with_serial.calls == [ + assert app.models.packaging.get_projects_with_serial.calls == [ pretend.call(), ] - assert result == dict(one=1, two=2, three=3) + assert result == d diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index 834c8c21b072..662a195c6560 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -203,6 +203,28 @@ def test_all_projects(projects, dbapp): assert dbapp.models.packaging.all_projects() == all_projects +@pytest.mark.parametrize(("num", "result"), [ + (None, [('three', 10000), ('one', 1110), ('two', 22)]), + (2, [('three', 10000), ('one', 1110)]), +]) +def test_top_projects(num, result, dbapp): + # Insert some data into the database + files = [ + ('one', 10, 'one-1.0.zip'), + ('one', 100, 'one-1.1.zip'), + ('one', 1000, 'one-1.2.zip'), + ('two', 2, 'two-1.0.zip'), + ('two', 20, 'two-1.2.zip'), + ('three', 10000, 'three-1.0.zip'), + ] + for name, downloads, filename in files: + dbapp.engine.execute(release_files.insert().values(name=name, + downloads=downloads, filename=filename)) + + top = dbapp.models.packaging.get_top_projects(num) + assert top == result + + @pytest.mark.parametrize(("name", "normalized"), [ ("foo_bar", "foo-bar"), ("Bar", "bar"), @@ -456,12 +478,12 @@ def test_get_last_serial(name, serial, dbapp): assert dbapp.models.packaging.get_last_serial(name) == serial -def test_get_packages_with_serial(dbapp): +def test_get_projects_with_serial(dbapp): dbapp.engine.execute(journals.insert().values(id=1, name='one')) dbapp.engine.execute(journals.insert().values(id=2, name='two')) dbapp.engine.execute(journals.insert().values(id=3, name='three')) - assert dbapp.models.packaging.get_packages_with_serial() == dict( + assert dbapp.models.packaging.get_projects_with_serial() == dict( one=1, two=2, three=3 diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index ce9c64a88d57..64e6a9c97f2e 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -47,13 +47,11 @@ def __init__(self, app, request): self.request = request def list_packages(self): - '''Retrieve a list of the package names registered with the package - index. - - Returns a list of name strings. - ''' projects = self.app.models.packaging.all_projects() return [project.name for project in projects] def list_packages_with_serial(self): - return self.app.models.packaging.get_packages_with_serial() + return self.app.models.packaging.get_projects_with_serial() + + def top_packages(self, num=None): + return self.app.models.packaging.get_top_projects(num) diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index a85cad09f249..7801e7ed5063 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -93,6 +93,18 @@ def all_projects(self): with self.engine.connect() as conn: return [Project(r["name"]) for r in conn.execute(query)] + def get_top_projects(self, num=None): + query = ( + select([release_files.c.name, func.sum(release_files.c.downloads)]) + .group_by(release_files.c.name) + .order_by(func.sum(release_files.c.downloads).desc()) + ) + if num: + query = query.limit(num) + + with self.engine.connect() as conn: + return [tuple(r) for r in conn.execute(query)] + def get_project(self, name): query = ( select([packages.c.name]) @@ -247,7 +259,7 @@ def get_last_serial(self, name=None): with self.engine.connect() as conn: return conn.execute(query).scalar() - def get_packages_with_serial(self): + def get_projects_with_serial(self): # return list of dict(name: max id) query = ( select([journals.c.name, func.max(journals.c.id)]) From 9dd61e058f6c902400c3fc58c901af1a39293c25 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 1 Nov 2013 16:12:36 +1100 Subject: [PATCH 03/14] add package_releases xml-rpc; implement _pypi_hidden filtering for the first time in warehouse ... probably contentious... --- tests/legacy/test_xmlrpc.py | 34 ++++++++++++++++++++++++++++++++++ tests/packaging/test_models.py | 22 ++++++++++++++++------ warehouse/legacy/xmlrpc.py | 4 ++++ warehouse/packaging/models.py | 9 +++++++-- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 923577bc94d6..ed9bd8fa79c1 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -128,6 +128,40 @@ def test_xmlrpc_top_packages(num, result): assert r == result +@pytest.mark.parametrize(("hidden", "result"), [ + (True, ['1', '2', '3', '4']), + (False, ['1', '2', '3']), +]) +def test_xmlrpc_package_releases(hidden, result): + if hidden: + result = ['1', '2', '3', '4'] + else: + result = ['1', '2', '3'] + + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_project_versions=pretend.call_recorder(lambda *a: result), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + if hidden: + r = interface.package_releases('name', True) + assert app.models.packaging.get_project_versions.calls == [ + pretend.call('name', True) + ] + else: + r = interface.package_releases('name') + assert app.models.packaging.get_project_versions.calls == [ + pretend.call('name', False) + ] + + assert r == result + + def test_xmlrpc_list_packages_with_serial(): d = dict(one=1, two=2, three=3) app = pretend.stub( diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index 662a195c6560..9de1b5097743 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -490,29 +490,39 @@ def test_get_projects_with_serial(dbapp): ) -def test_get_project_versions(dbapp): +@pytest.mark.parametrize(("show_hidden", "res"), [ + (True, ["4.0", "3.0", "2.0", "1.0"]), + (False, ["3.0", "2.0", "1.0"]), +]) +def test_get_project_versions(show_hidden, res, dbapp): dbapp.engine.execute(packages.insert().values(name="test-project")) dbapp.engine.execute(releases.insert().values( name="test-project", version="2.0", _pypi_ordering=2, + _pypi_hidden=False, )) dbapp.engine.execute(releases.insert().values( name="test-project", version="1.0", _pypi_ordering=1, + _pypi_hidden=False, )) dbapp.engine.execute(releases.insert().values( name="test-project", version="3.0", _pypi_ordering=3, + _pypi_hidden=False, + )) + dbapp.engine.execute(releases.insert().values( + name="test-project", + version="4.0", + _pypi_ordering=4, + _pypi_hidden=True, )) - assert dbapp.models.packaging.get_project_versions("test-project") == [ - "3.0", - "2.0", - "1.0", - ] + assert dbapp.models.packaging.get_project_versions("test-project", + show_hidden) == res def test_get_release(dbapp): diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 64e6a9c97f2e..03257d923a01 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -55,3 +55,7 @@ def list_packages_with_serial(self): def top_packages(self, num=None): return self.app.models.packaging.get_top_projects(num) + + def package_releases(self, name, show_hidden=False): + return self.app.models.packaging.get_project_versions(name, + show_hidden) diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index 7801e7ed5063..c958e0d57ebf 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -22,6 +22,7 @@ from collections import namedtuple from sqlalchemy.sql import and_, select, func +from sqlalchemy.sql.expression import false from warehouse import models from warehouse.accounts.tables import users, emails @@ -269,13 +270,17 @@ def get_projects_with_serial(self): with self.engine.connect() as conn: return dict(r for r in conn.execute(query)) - def get_project_versions(self, project): + def get_project_versions(self, project, show_hidden=False): query = ( select([releases.c.version]) .where(releases.c.name == project) - .order_by(releases.c._pypi_ordering.desc()) ) + if not show_hidden: + query = query.where(releases.c._pypi_hidden == false()) + + query = query.order_by(releases.c._pypi_ordering.desc()) + with self.engine.connect() as conn: return [r["version"] for r in conn.execute(query)] From fc91b40aa8078599e1005d5a7dc79dd2c545f073 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 1 Nov 2013 16:56:44 +1100 Subject: [PATCH 04/14] implement release_urls xml-rpc --- tests/legacy/test_xmlrpc.py | 73 +++++++++++++++++++++++++++++++++++++ warehouse/legacy/xmlrpc.py | 16 ++++++++ 2 files changed, 89 insertions(+) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index ed9bd8fa79c1..50f0e104b978 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -180,3 +180,76 @@ def test_xmlrpc_list_packages_with_serial(): pretend.call(), ] assert result == d + + +@pytest.mark.parametrize("pgp", [True, False]) +def test_release_urls(pgp, monkeypatch): + downloads = [ + dict( + name="spam", + url='/packages/source/t/spam/spam-1.0.tar.gz', + version="1.0", + filename="spam-1.0.tar.gz", + python_version="source", + packagetype="sdist", + md5_digest="0cc175b9c0f1b6a831c399e269772661", + downloads=10, + size=1234, + pgp_url='/packages/source/t/spam/spam-1.0.tar.gz.sig' + if pgp else None, + comment_text='download for great justice', + ), + dict( + name="spam", + url='/packages/source/t/spam/spam-1.0.zip', + version="1.0", + filename="spam-1.0.zip", + python_version="source", + packagetype="sdist", + md5_digest="0cc175b3c0f1b6a831c399e269772661", + downloads=12, + size=1235, + pgp_url='/packages/source/t/spam/spam-1.0.zip.sig' + if pgp else None, + comment_text=None, + ) + ] + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_downloads=pretend.call_recorder(lambda *a: downloads), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + result = interface.release_urls('spam', '1.0') + + assert app.models.packaging.get_downloads.calls == [ + pretend.call('spam', '1.0'), + ] + assert result == [ + dict( + url='/packages/source/t/spam/spam-1.0.tar.gz', + packagetype="sdist", + filename="spam-1.0.tar.gz", + size=1234, + md5_digest="0cc175b9c0f1b6a831c399e269772661", + downloads=10, + has_sig=pgp, + python_version="source", + comment_text='download for great justice', + ), + dict( + url='/packages/source/t/spam/spam-1.0.zip', + packagetype="sdist", + filename="spam-1.0.zip", + size=1235, + md5_digest="0cc175b3c0f1b6a831c399e269772661", + downloads=12, + has_sig=pgp, + python_version="source", + comment_text=None, + ) + ] diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 03257d923a01..76642077b3f8 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -59,3 +59,19 @@ def top_packages(self, num=None): def package_releases(self, name, show_hidden=False): return self.app.models.packaging.get_project_versions(name, show_hidden) + + def release_urls(self, name, version): + l = [] + for r in self.app.models.packaging.get_downloads(name, version): + l.append(dict( + url=r['url'], + packagetype=r['packagetype'], + filename=r['filename'], + size=r['size'], + md5_digest=r['md5_digest'], + downloads=r['downloads'], + has_sig=r['pgp_url'] is not None, + python_version=r['python_version'], + comment_text=r['comment_text'], + )) + return l From d61ac054601f4686ac74574fcb7d4b0057a4839e Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 1 Nov 2013 17:52:22 +1100 Subject: [PATCH 05/14] add release_data xml-rpc call; add stable_version to model get_release code --- tests/legacy/test_xmlrpc.py | 81 ++++++++++++++++++++++++++++++++++ tests/packaging/test_models.py | 4 +- warehouse/legacy/xmlrpc.py | 23 ++++++++++ warehouse/packaging/models.py | 2 + 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 50f0e104b978..0adb95ae5cb9 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -14,6 +14,8 @@ from __future__ import absolute_import, division, print_function from __future__ import unicode_literals +import datetime + import pretend import pytest @@ -253,3 +255,82 @@ def test_release_urls(pgp, monkeypatch): comment_text=None, ) ] + + +def test_release_data(monkeypatch): + resp = dict( + name="spam", + version="1.0", + stable_version="2.0", + author="John Doe", + author_email="john.doe@example.com", + maintainer=None, + maintainer_email=None, + home_page="https://example.com/", + license="Apache License v2.0", + summary="A Test Project", + description="A Longer Test Project", + keywords="foo,bar,wat", + platform="All", + download_url=("https://example.com/downloads/" + "test-project-1.0.tar.gz"), + requires_dist=["requests (>=2.0)"], + provides_dist=["test-project-old"], + project_url={"Repository": "git://git.example.com/"}, + created=datetime.datetime.utcnow(), + ) + docs = "https://pythonhosted.org/spam/" + cfiers = ['Section A :: Subsection B :: Aisle 3', 'Section B'] + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_release=pretend.call_recorder(lambda *a: resp), + get_documentation_url=pretend.call_recorder(lambda *a: docs), + get_download_counts=pretend.call_recorder(lambda *a: 10), + get_classifiers=pretend.call_recorder(lambda *a: cfiers), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + result = interface.release_data('spam', '1.0') + + assert app.models.packaging.get_release.calls == [ + pretend.call('spam', '1.0'), + ] + + info = dict(resp) + del info['created'] + info.update( + package_url='http://pypi.python.org/pypi/spam', + release_url='http://pypi.python.org/pypi/spam/1.0', + docs_url=docs, + downloads=10, + classifiers=cfiers, + created=resp['created'] # Not in the legacy spec but meh, whatever + ) + assert result == info + + +def test_release_data_missing(monkeypatch): + def f(*a): + raise IndexError() + + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_release=pretend.call_recorder(f), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + result = interface.release_data('spam', '1.0') + + assert app.models.packaging.get_release.calls == [ + pretend.call('spam', '1.0'), + ] + + assert result == {} diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index 9de1b5097743..8696bd8de23e 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -528,7 +528,8 @@ def test_get_project_versions(show_hidden, res, dbapp): def test_get_release(dbapp): created = datetime.datetime.utcnow() - dbapp.engine.execute(packages.insert().values(name="test-project")) + dbapp.engine.execute(packages.insert().values(name="test-project", + stable_version='2.0')) dbapp.engine.execute(releases.insert().values( created=created, name="test-project", @@ -605,6 +606,7 @@ def test_get_release(dbapp): assert test_release == { "name": "test-project", "version": "1.0", + "stable_version": "2.0", "author": "John Doe", "author_email": "john.doe@example.com", "maintainer": "Jane Doe", diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 76642077b3f8..0ed4f741da54 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -75,3 +75,26 @@ def release_urls(self, name, version): comment_text=r['comment_text'], )) return l + + def release_data(self, name, version): + model = self.app.models.packaging + try: + info = model.get_release(name, version) + except IndexError: + # the CURRENT model code will raise an IndexError on missing + # package but this should be altered + return {} + + info['classifiers'] = model.get_classifiers(name, version) + info['package_url'] = 'http://pypi.python.org/pypi/%s' % name + info['release_url'] = 'http://pypi.python.org/pypi/%s/%s' % (name, + version) + info['docs_url'] = model.get_documentation_url(name) + info['downloads'] = model.get_download_counts(name) + + # make the data XML-RPC-happy (no explicit null allowed here!) + for k in info: + if info[k] is None: + info[k] = '' + + return info diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index c958e0d57ebf..8b4b0890e94b 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -349,6 +349,7 @@ def get_release(self, project, version): select([ releases.c.name, releases.c.version, + packages.c.stable_version, releases.c.author, releases.c.author_email, releases.c.maintainer, @@ -366,6 +367,7 @@ def get_release(self, project, version): releases.c.name == project, releases.c.version == version, )) + .where(packages.c.name == project) .order_by(releases.c._pypi_ordering.desc()) .limit(1) ) From f9178993340d65e5c52261f6909d3b6a36d0add4 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 09:36:49 +1100 Subject: [PATCH 06/14] clean up test --- tests/legacy/test_xmlrpc.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 0adb95ae5cb9..5cdfa172013c 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -272,13 +272,14 @@ def test_release_data(monkeypatch): description="A Longer Test Project", keywords="foo,bar,wat", platform="All", - download_url=("https://example.com/downloads/" - "test-project-1.0.tar.gz"), + download_url="https://example.com/downloads/test-project-1.0.tar.gz", requires_dist=["requests (>=2.0)"], provides_dist=["test-project-old"], project_url={"Repository": "git://git.example.com/"}, created=datetime.datetime.utcnow(), ) + # snapshot that info now for comparison later + info = dict(resp) docs = "https://pythonhosted.org/spam/" cfiers = ['Section A :: Subsection B :: Aisle 3', 'Section B'] app = pretend.stub( @@ -300,15 +301,15 @@ def test_release_data(monkeypatch): pretend.call('spam', '1.0'), ] - info = dict(resp) - del info['created'] + # modify the model response data according to the expected mutation info.update( package_url='http://pypi.python.org/pypi/spam', release_url='http://pypi.python.org/pypi/spam/1.0', docs_url=docs, downloads=10, classifiers=cfiers, - created=resp['created'] # Not in the legacy spec but meh, whatever + maintainer='', # converted from None + maintainer_email='', # converted from None ) assert result == info From 8d79a4cc8697655ca82550fdcbc475cb07833585 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 10:38:18 +1100 Subject: [PATCH 07/14] add get_releases_since and deprecate use of _pypi_hidden --- docs/api-reference/xml-rpc.rst | 19 ++++++++-- tests/legacy/test_xmlrpc.py | 54 +++++++++++++++++----------- tests/packaging/test_models.py | 64 ++++++++++++++++++++++++++++------ warehouse/legacy/xmlrpc.py | 7 ++-- warehouse/packaging/models.py | 17 ++++++--- 5 files changed, 119 insertions(+), 42 deletions(-) diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst index ec6ffe9ed57c..b8cbbb2a23fc 100644 --- a/docs/api-reference/xml-rpc.rst +++ b/docs/api-reference/xml-rpc.rst @@ -31,6 +31,14 @@ Example usage: 'upload_time': , 'url': 'http://pypi.python.org/packages/any/r/roundup/roundup-1.1.2.win32.exe'}] +Changes to Legacy API +--------------------- + +``package_releases`` "show_hidden" flag is now ignored. All versions are +returned. + + + Package Querying ---------------- @@ -39,9 +47,10 @@ Package Querying Returns a list of name strings. ``package_releases(package_name, show_hidden=False)`` - Retrieve a list of the releases registered for the given package_name. - Returns a list with all version strings if show_hidden is True or only the - non-hidden ones otherwise. + Retrieve a list of the releases registered for the given package_name, + ordered by version. + + The "show_hidden" flag is now ignored. All versions are returned. ``package_roles(package_name)`` Retrieve a list of users and their attributes roles for a given package_name. @@ -135,6 +144,10 @@ Package Querying Retrieve the sorted list of packages ranked by number of downloads. Optionally limit the list to the number given. +``updated_releases(since)`` + Retrieve a list of package releases made since the given timestamp. The + releases will be listed in descending release date. + Mirroring Support ----------------- diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 5cdfa172013c..446879f73879 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -130,16 +130,8 @@ def test_xmlrpc_top_packages(num, result): assert r == result -@pytest.mark.parametrize(("hidden", "result"), [ - (True, ['1', '2', '3', '4']), - (False, ['1', '2', '3']), -]) -def test_xmlrpc_package_releases(hidden, result): - if hidden: - result = ['1', '2', '3', '4'] - else: - result = ['1', '2', '3'] - +def test_xmlrpc_package_releases(): + result = ['1', '2', '3', '4'] app = pretend.stub( models=pretend.stub( packaging=pretend.stub( @@ -150,18 +142,38 @@ def test_xmlrpc_package_releases(hidden, result): interface = xmlrpc.Interface(app, pretend.stub()) - if hidden: - r = interface.package_releases('name', True) - assert app.models.packaging.get_project_versions.calls == [ - pretend.call('name', True) - ] - else: - r = interface.package_releases('name') - assert app.models.packaging.get_project_versions.calls == [ - pretend.call('name', False) - ] + assert interface.package_releases('name') == ['1', '2', '3', '4'] - assert r == result + assert app.models.packaging.get_project_versions.calls == [ + pretend.call('name') + ] + + +def test_xmlrpc_updated_releases(): + now = datetime.datetime.now() + + result = [ + dict(name='one', version='1', created=now, summary='text'), + dict(name='two', version='2', created=now, summary='text'), + dict(name='two', version='3', created=now, summary='text'), + dict(name='three', version='4', created=now, summary='text')] + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_releases_since=pretend.call_recorder(lambda *a: result), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + old = now = datetime.timedelta(days=1) + assert interface.updated_releases(old) == \ + [('one', '1'), ('two', '2'), ('two', '3'), ('three', '4')] + + assert app.models.packaging.get_releases_since.calls == [ + pretend.call(old) + ] def test_xmlrpc_list_packages_with_serial(): diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index 8696bd8de23e..3bd9969e5a5d 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -188,6 +188,56 @@ def test_get_recently_updated(dbapp): ] +def test_get_releases_since(dbapp): + dbapp.engine.execute(packages.insert().values(name="foo1")) + dbapp.engine.execute(packages.insert().values(name="foo2")) + dbapp.engine.execute(packages.insert().values(name="foo3")) + + now = datetime.datetime.utcnow() + + dbapp.engine.execute(releases.insert().values( + name="foo2", version="1.0", + created=now - datetime.timedelta(seconds=10), + )) + dbapp.engine.execute(releases.insert().values( + name="foo3", version="2.0", + created=now - datetime.timedelta(seconds=9), + )) + dbapp.engine.execute(releases.insert().values( + name="foo1", version="1.0", + created=now - datetime.timedelta(seconds=4), + )) + dbapp.engine.execute(releases.insert().values( + name="foo3", version="1.0", + created=now - datetime.timedelta(seconds=3), + )) + dbapp.engine.execute(releases.insert().values( + name="foo1", version="2.0", created=now, + )) + + since = now - datetime.timedelta(seconds=5) + assert dbapp.models.packaging.get_releases_since(since) == [ + { + "name": "foo1", + "version": "2.0", + "summary": None, + "created": now, + }, + { + "name": "foo3", + "version": "1.0", + "summary": None, + "created": now - datetime.timedelta(seconds=3), + }, + { + "name": "foo1", + "version": "1.0", + "summary": None, + "created": now - datetime.timedelta(seconds=4), + }, + ] + + @pytest.mark.parametrize("projects", [ ["foo", "bar", "zap"], ["fail", "win", "YeS"], @@ -490,39 +540,31 @@ def test_get_projects_with_serial(dbapp): ) -@pytest.mark.parametrize(("show_hidden", "res"), [ - (True, ["4.0", "3.0", "2.0", "1.0"]), - (False, ["3.0", "2.0", "1.0"]), -]) -def test_get_project_versions(show_hidden, res, dbapp): +def test_get_project_versions(dbapp): dbapp.engine.execute(packages.insert().values(name="test-project")) dbapp.engine.execute(releases.insert().values( name="test-project", version="2.0", _pypi_ordering=2, - _pypi_hidden=False, )) dbapp.engine.execute(releases.insert().values( name="test-project", version="1.0", _pypi_ordering=1, - _pypi_hidden=False, )) dbapp.engine.execute(releases.insert().values( name="test-project", version="3.0", _pypi_ordering=3, - _pypi_hidden=False, )) dbapp.engine.execute(releases.insert().values( name="test-project", version="4.0", _pypi_ordering=4, - _pypi_hidden=True, )) - assert dbapp.models.packaging.get_project_versions("test-project", - show_hidden) == res + assert dbapp.models.packaging.get_project_versions("test-project") == \ + ["4.0", "3.0", "2.0", "1.0"] def test_get_release(dbapp): diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 0ed4f741da54..1a124ddbf8d6 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -57,8 +57,11 @@ def top_packages(self, num=None): return self.app.models.packaging.get_top_projects(num) def package_releases(self, name, show_hidden=False): - return self.app.models.packaging.get_project_versions(name, - show_hidden) + return self.app.models.packaging.get_project_versions(name) + + def updated_releases(self, since): + result = self.app.models.packaging.get_releases_since(since) + return [(row['name'], row['version']) for row in result] def release_urls(self, name, version): l = [] diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index 8b4b0890e94b..793c0913c59f 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -22,7 +22,6 @@ from collections import namedtuple from sqlalchemy.sql import and_, select, func -from sqlalchemy.sql.expression import false from warehouse import models from warehouse.accounts.tables import users, emails @@ -88,6 +87,17 @@ def get_recently_updated(self, num=10): with self.engine.connect() as conn: return [dict(r) for r in conn.execute(query)] + def get_releases_since(self, since): + query = ( + select([releases.c.name, releases.c.version, + releases.c.created, releases.c.summary]) + .where(releases.c.created > since) + .order_by(releases.c.created.desc()) + ) + + with self.engine.connect() as conn: + return [dict(r) for r in conn.execute(query)] + def all_projects(self): query = select([packages.c.name]).order_by(func.lower(packages.c.name)) @@ -270,15 +280,12 @@ def get_projects_with_serial(self): with self.engine.connect() as conn: return dict(r for r in conn.execute(query)) - def get_project_versions(self, project, show_hidden=False): + def get_project_versions(self, project): query = ( select([releases.c.version]) .where(releases.c.name == project) ) - if not show_hidden: - query = query.where(releases.c._pypi_hidden == false()) - query = query.order_by(releases.c._pypi_ordering.desc()) with self.engine.connect() as conn: From 5b36824595777b059d51e3fdd8329cb569c0fe50 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 14:00:13 +1100 Subject: [PATCH 08/14] add XML-RPC changelog, switching to SQL in code rather than bizarro code --- tests/legacy/test_xmlrpc.py | 68 ++++++++++++++++++++++++++++++++++ tests/packaging/test_models.py | 58 +++++++++++++++++++++++++++++ warehouse/legacy/xmlrpc.py | 7 ++++ warehouse/packaging/models.py | 12 ++++++ 4 files changed, 145 insertions(+) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 446879f73879..8f46b249abb1 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -149,6 +149,47 @@ def test_xmlrpc_package_releases(): ] +@pytest.mark.parametrize("with_ids", [False, True]) +def test_xmlrpc_changelog(with_ids): + now = datetime.datetime.now() + old = datetime.datetime.now() - datetime.timedelta(days=1) + now_plus_1 = datetime.datetime.now() + datetime.timedelta(days=1) + now_plus_2 = datetime.datetime.now() + datetime.timedelta(days=2) + data = [ + dict(name='one', version='1', submitted_date=now, + action='created', id=1), + dict(name='two', version='2', submitted_date=now, + action='new release', id=2), + dict(name='one', version='2', submitted_date=now_plus_1, + action='new release', id=3), + dict(name='one', version='3', submitted_date=now_plus_2, + action='new release', id=4), + ] + result = [ + ('one', '1', now, 'created', 1), + ('two', '2', now, 'new release', 2), + ('one', '2', now_plus_1, 'new release', 3), + ('one', '3', now_plus_2, 'new release', 4), + ] + if not with_ids: + result = [r[:4] for r in result] + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_changelog=pretend.call_recorder(lambda *a: data), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + assert interface.changelog(old, with_ids) == result + + assert app.models.packaging.get_changelog.calls == [ + pretend.call(old) + ] + + def test_xmlrpc_updated_releases(): now = datetime.datetime.now() @@ -176,6 +217,33 @@ def test_xmlrpc_updated_releases(): ] +def test_xmlrpc_update_releases(): + now = datetime.datetime.now() + + result = [ + dict(name='one', version='1', created=now, summary='text'), + dict(name='two', version='2', created=now, summary='text'), + dict(name='two', version='3', created=now, summary='text'), + dict(name='three', version='4', created=now, summary='text')] + app = pretend.stub( + models=pretend.stub( + packaging=pretend.stub( + get_releases_since=pretend.call_recorder(lambda *a: result), + ), + ), + ) + + interface = xmlrpc.Interface(app, pretend.stub()) + + old = now = datetime.timedelta(days=1) + assert interface.updated_releases(old) == \ + [('one', '1'), ('two', '2'), ('two', '3'), ('three', '4')] + + assert app.models.packaging.get_releases_since.calls == [ + pretend.call(old) + ] + + def test_xmlrpc_list_packages_with_serial(): d = dict(one=1, two=2, three=3) app = pretend.stub( diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index 3bd9969e5a5d..3dd5bce9b404 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -238,6 +238,64 @@ def test_get_releases_since(dbapp): ] +def test_get_changelog(dbapp): + now = datetime.datetime.utcnow() + + def create(name, delta): + dbapp.engine.execute(packages.insert().values(name=name)) + dbapp.engine.execute(journals.insert().values(name=name, version=None, + submitted_date=now - delta, action="create", id=create.id)) + create.id += 1 + create.id = 1 + create("foo1", datetime.timedelta(seconds=4)) + create("foo2", datetime.timedelta(seconds=5)) + create("foo3", datetime.timedelta(seconds=10)) + + def release(name, version, delta): + dbapp.engine.execute(releases.insert().values(name=name, + version=version, created=now - delta)) + dbapp.engine.execute(journals.insert().values(id=create.id, name=name, + version=version, submitted_date=now - delta, action="new release")) + create.id += 1 + release("foo2", "1.0", datetime.timedelta(seconds=10)) + release("foo3", "2.0", datetime.timedelta(seconds=9)) + release("foo1", "1.0", datetime.timedelta(seconds=3)) + release("foo3", "1.0", datetime.timedelta(seconds=2)) + release("foo1", "2.0", datetime.timedelta(seconds=1)) + + since = now - datetime.timedelta(seconds=5) + assert dbapp.models.packaging.get_changelog(since) == [ + { + "name": "foo1", + "version": "2.0", + "action": "new release", + "submitted_date": now - datetime.timedelta(seconds=1), + "id": 8, + }, + { + "name": "foo3", + "version": "1.0", + "action": "new release", + "submitted_date": now - datetime.timedelta(seconds=2), + "id": 7, + }, + { + "name": "foo1", + "version": "1.0", + "action": "new release", + "submitted_date": now - datetime.timedelta(seconds=3), + "id": 6, + }, + { + "name": "foo1", + "version": None, + "action": "create", + "submitted_date": now - datetime.timedelta(seconds=4), + "id": 1, + }, + ] + + @pytest.mark.parametrize("projects", [ ["foo", "bar", "zap"], ["fail", "win", "YeS"], diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 1a124ddbf8d6..5712d7d6b046 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -63,6 +63,13 @@ def updated_releases(self, since): result = self.app.models.packaging.get_releases_since(since) return [(row['name'], row['version']) for row in result] + def changelog(self, since, with_ids=False): + result = self.app.models.packaging.get_changelog(since) + keys = 'name version submitted_date action'.split() + if with_ids: + keys.append('id') + return [tuple(row[key] for key in keys) for row in result] + def release_urls(self, name, version): l = [] for r in self.app.models.packaging.get_downloads(name, version): diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index 793c0913c59f..171908081335 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -541,3 +541,15 @@ def get_bugtrack_url(self, project): with self.engine.connect() as conn: return conn.execute(query).scalar() + + # + # Mirroring support + # + def get_changelog(self, since): + query = '''SELECT name, version, submitted_date, action, id + FROM journals + WHERE journals.submitted_date > %s + ORDER BY submitted_date DESC + ''' + with self.engine.connect() as conn: + return [dict(r) for r in conn.execute(query, since)] From 9f8667728adeb65ea6c64e3165be61a560410a56 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 14:01:13 +1100 Subject: [PATCH 09/14] add htmlcov to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7fccf4b877b8..b66c0be77783 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ sdist develop-eggs .installed.cfg node_modules +htmlcov # Documentation build directory docs/_build From 50b5230f8bfffe447dd4cc62138cacf98ca3ecde Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 14:52:46 +1100 Subject: [PATCH 10/14] s/http/https --- docs/api-reference/xml-rpc.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst index b8cbbb2a23fc..a945314879f8 100644 --- a/docs/api-reference/xml-rpc.rst +++ b/docs/api-reference/xml-rpc.rst @@ -6,7 +6,7 @@ Example usage: >>> import xmlrpclib >>> import pprint - >>> client = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') + >>> client = xmlrpclib.ServerProxy('https://pypi.python.org/pypi') >>> client.package_releases('roundup') ['1.4.10'] >>> pprint.pprint(client.release_urls('roundup', '1.4.10')) @@ -19,7 +19,7 @@ Example usage: 'python_version': 'source', 'size': 876455, 'upload_time': , - 'url': 'http://pypi.python.org/packages/source/r/roundup/roundup-1.1.2.tar.gz'}, + 'url': 'https://pypi.python.org/packages/source/r/roundup/roundup-1.1.2.tar.gz'}, {'comment_text': '', 'downloads': 2067, 'filename': 'roundup-1.1.2.win32.exe', @@ -29,7 +29,7 @@ Example usage: 'python_version': 'any', 'size': 614270, 'upload_time': , - 'url': 'http://pypi.python.org/packages/any/r/roundup/roundup-1.1.2.win32.exe'}] + 'url': 'https://pypi.python.org/packages/any/r/roundup/roundup-1.1.2.win32.exe'}] Changes to Legacy API --------------------- From adde84bad32c7e92cacb28cc36c9668753201dcf Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 15:00:00 +1100 Subject: [PATCH 11/14] actually, let's kill stable_version since PEP 440 does a better job --- tests/legacy/test_xmlrpc.py | 2 +- tests/packaging/test_models.py | 4 +--- warehouse/legacy/xmlrpc.py | 1 + warehouse/packaging/models.py | 11 +++++------ 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/legacy/test_xmlrpc.py b/tests/legacy/test_xmlrpc.py index 8f46b249abb1..590b9e5e16b3 100644 --- a/tests/legacy/test_xmlrpc.py +++ b/tests/legacy/test_xmlrpc.py @@ -341,7 +341,6 @@ def test_release_data(monkeypatch): resp = dict( name="spam", version="1.0", - stable_version="2.0", author="John Doe", author_email="john.doe@example.com", maintainer=None, @@ -390,6 +389,7 @@ def test_release_data(monkeypatch): classifiers=cfiers, maintainer='', # converted from None maintainer_email='', # converted from None + stable_version='', # filled in as no-op ) assert result == info diff --git a/tests/packaging/test_models.py b/tests/packaging/test_models.py index ea70fe9050d9..fcbc1c6e9667 100644 --- a/tests/packaging/test_models.py +++ b/tests/packaging/test_models.py @@ -633,8 +633,7 @@ def test_get_project_versions(dbapp): def test_get_release(dbapp): created = datetime.datetime.utcnow() - dbapp.engine.execute(packages.insert().values(name="test-project", - stable_version='2.0')) + dbapp.engine.execute(packages.insert().values(name="test-project")) dbapp.engine.execute(releases.insert().values( created=created, name="test-project", @@ -711,7 +710,6 @@ def test_get_release(dbapp): assert test_release == { "name": "test-project", "version": "1.0", - "stable_version": "2.0", "author": "John Doe", "author_email": "john.doe@example.com", "maintainer": "Jane Doe", diff --git a/warehouse/legacy/xmlrpc.py b/warehouse/legacy/xmlrpc.py index 5712d7d6b046..b88f982aae5a 100644 --- a/warehouse/legacy/xmlrpc.py +++ b/warehouse/legacy/xmlrpc.py @@ -95,6 +95,7 @@ def release_data(self, name, version): # package but this should be altered return {} + info['stable_version'] = '' # legacy; never actually correct info['classifiers'] = model.get_classifiers(name, version) info['package_url'] = 'http://pypi.python.org/pypi/%s' % name info['release_url'] = 'http://pypi.python.org/pypi/%s/%s' % (name, diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py index 8f5b8df2cb10..b566cb9ef06f 100644 --- a/warehouse/packaging/models.py +++ b/warehouse/packaging/models.py @@ -299,12 +299,11 @@ def get_downloads(self, project, version): def get_release(self, project, version): query = \ """ SELECT - r.name, version, stable_version, author, author_email, - maintainer, maintainer_email, home_page, license, summary, - description, keywords, platform, download_url, r.created - FROM releases r, packages p - WHERE r.name = %(project)s AND version = %(version)s - AND r.name = p.name + name, version, author, author_email, maintainer, + maintainer_email, home_page, license, summary, description, + keywords, platform, download_url, created + FROM releases + WHERE name = %(project)s AND version = %(version)s ORDER BY _pypi_ordering DESC LIMIT 1 """ From 23f273184e73b73ebb780bf04c7108f71a36fac5 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 15:03:17 +1100 Subject: [PATCH 12/14] actually, let's kill stable_version since PEP 440 does a better job --- docs/api-reference/xml-rpc.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst index a945314879f8..49bc281ec8ea 100644 --- a/docs/api-reference/xml-rpc.rst +++ b/docs/api-reference/xml-rpc.rst @@ -37,6 +37,8 @@ Changes to Legacy API ``package_releases`` "show_hidden" flag is now ignored. All versions are returned. +``release_data`` "stable_version" is always an empty string. It was never +fully supported anyway. Package Querying From 99c3acb0518ff5bb2332c3cf18f5fc241fbab23c Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 15:04:35 +1100 Subject: [PATCH 13/14] actually, let's kill stable_version since PEP 440 does a better job --- docs/api-reference/xml-rpc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst index 49bc281ec8ea..1aba3323ea4e 100644 --- a/docs/api-reference/xml-rpc.rst +++ b/docs/api-reference/xml-rpc.rst @@ -84,7 +84,7 @@ Package Querying Returns a dict with keys for: - name - version - - stable_version + - stable_version (always an empty string) - author - author_email - maintainer From 1961b13dfbf1a9b5c4bbd7e9e3ca874ca6257960 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 7 Nov 2013 15:27:49 +1100 Subject: [PATCH 14/14] make this literal block so it isn't execute as a test --- docs/api-reference/xml-rpc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/xml-rpc.rst b/docs/api-reference/xml-rpc.rst index 1aba3323ea4e..0fddb455bc40 100644 --- a/docs/api-reference/xml-rpc.rst +++ b/docs/api-reference/xml-rpc.rst @@ -2,7 +2,7 @@ PyPI's XML-RPC methods ====================== -Example usage: +Example usage:: >>> import xmlrpclib >>> import pprint