Repository: pypi/warehouse

Issue:
Deprecate & Remove X-PyPI-Last-Serial on File Responses
Ideally we'd like to not serve the file responses via the Warehouse web application, however we currently need to do this in order to add the `X-PyPI-Last-Serial` responses. Bandersnatch is currently using this, so it would be great to investigate what it's being used for and if we can safely deprecate and remove it.



Discussion:
This isn't just _used by_ Bandersnatch, it was added specifically _for_
Bandersnatch :)

On Sun, 15 Mar 2015 at 17:52 Donald Stufft notifications@github.com wrote:

> Ideally we'd like to not serve the file responses via the Warehouse web
> application, however we currently need to do this in order to add the
> X-PyPI-Last-Serial responses. Bandersnatch is currently using this, so it
> would be great to investigate what it's being used for and if we can safely
> deprecate and remove it.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pypa/warehouse/issues/433.

Yea, I just wasn't sure if anyone else was using it or not.

Ok, I've talked to @ctheune and Bandersnatch is going to remove the need for this. Changes to PyPI (disallowing delete + reupload as a general case) remove the primary need for this since the contents located at a file URL are likely never going to change except to possibly be deleted.


Resolving patch:
diff --git a/warehouse/packaging/views.py b/warehouse/packaging/views.py
--- a/warehouse/packaging/views.py
+++ b/warehouse/packaging/views.py
@@ -15,14 +15,13 @@
 from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
 from pyramid.response import FileIter, Response
 from pyramid.view import view_config
-from sqlalchemy import func
 from sqlalchemy.orm.exc import NoResultFound
 
 from warehouse.accounts.models import User
 from warehouse.cache.http import cache_control
 from warehouse.cache.origin import origin_cache
 from warehouse.packaging.interfaces import IDownloadStatService
-from warehouse.packaging.models import Release, File, Role, JournalEntry
+from warehouse.packaging.models import Release, File, Role
 
 
 @view_config(
@@ -113,9 +112,7 @@ def packages(request):
     # The amount of logic that we can do in this view is very limited, this
     # view needs to be able to be handled by Fastly directly hitting S3 instead
     # of actually hitting this view. This more or less means that we're limited
-    # to just setting headers and serving the actual file. In addition the
-    # headers that we can set, have to be able to be determined at file upload
-    # time instead of dynamically.
+    # to just serving the actual file.
 
     # Grab the path of the file that we're attempting to serve
     path = request.matchdict["path"]
@@ -159,7 +156,7 @@ def packages(request):
     if path == file_.path:
         content_length = file_.size
 
-    resp = Response(
+    return Response(
         # If we have a wsgi.file_wrapper, we'll want to use that so that, if
         # possible, this will use an optimized method of sending. Otherwise
         # we'll just use Pyramid's FileIter as a fallback.
@@ -185,16 +182,3 @@ def packages(request):
         # they handle downloading this response.
         content_length=content_length,
     )
-
-    # We also need to get the X-PyPI-Last-Serial for the project associated
-    # with this file. Bandersnatch (and other mirroring clients) will use this
-    # to determine what kind of action to take if the MD5 hash does not match
-    # what it expected.
-    serial = (
-        request.db.query(func.max(JournalEntry.id))
-                  .filter(JournalEntry.name == file_.name)
-                  .scalar()
-    )
-    resp.headers["X-PyPI-Last-Serial"] = serial or 0
-
-    return resp


Test patch:
diff --git a/tests/packaging/test_views.py b/tests/packaging/test_views.py
--- a/tests/packaging/test_views.py
+++ b/tests/packaging/test_views.py
@@ -25,7 +25,6 @@
 from ..common.db.accounts import UserFactory
 from ..common.db.packaging import (
     ProjectFactory, ReleaseFactory, FileFactory, RoleFactory,
-    JournalEntryFactory,
 )
 
 
@@ -242,12 +241,6 @@ def test_serves_package_file(self, db_request, pyramid_config):
             filename="{}-{}.tar.gz".format(project.name, release.version),
             python_version="source",
         )
-        user = UserFactory.create(session=db_request.db)
-        je = JournalEntryFactory.create(
-            session=db_request.db,
-            name=project.name,
-            submitted_by=user.username,
-        )
 
         path = "source/{}/{}/{}".format(
             project.name[0], project.name, file_.filename
@@ -270,7 +263,6 @@ def test_serves_package_file(self, db_request, pyramid_config):
         assert resp.etag == file_.md5_digest
         assert resp.last_modified == last_modified
         assert resp.content_length == 27
-        assert resp.headers["X-PyPI-Last-Serial"] == je.id
         # This needs to be last, as accessing resp.body sets the content_length
         assert resp.body == b"some data for the fake file"
 
@@ -287,12 +279,6 @@ def test_serves_signature_file(self, db_request, pyramid_config):
             filename="{}-{}.tar.gz".format(project.name, release.version),
             python_version="source",
         )
-        user = UserFactory.create(session=db_request.db)
-        je = JournalEntryFactory.create(
-            session=db_request.db,
-            name=project.name,
-            submitted_by=user.username,
-        )
 
         path = "source/{}/{}/{}.asc".format(
             project.name[0], project.name, file_.filename
@@ -315,6 +301,5 @@ def test_serves_signature_file(self, db_request, pyramid_config):
         assert resp.etag == file_.md5_digest
         assert resp.last_modified == last_modified
         assert resp.content_length is None
-        assert resp.headers["X-PyPI-Last-Serial"] == je.id
         # This needs to be last, as accessing resp.body sets the content_length
         assert resp.body == b"some data for the fake file"