internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: PaddlePaddle/PaddleDetection

Issue:
当返回的roidbs为空时，卡在if isinstance(item, collections.Sequence) and len(item) == 0
    def _load_batch(self):
        batch = []
        bs = 0
        while bs != self._batch_size:
            if self._pos >= self.size():
                break
            pos = self.indexes[self._pos]
            sample = copy.deepcopy(self._roidbs[pos])
            sample["curr_iter"] = self._curr_iter
            self._pos += 1

            if self._drop_empty and self._fields and 'gt_bbox' in sample:
                if _has_empty(sample['gt_bbox']):

经自己测试 如果record有数据 但是roidbs里面bbox数据为[]，会一直循环 跳不出去 望加个判断修复下


Discussion:
感谢反馈，我们修复一下，谢谢。

Resolving patch:
diff --git a/ppdet/data/source/coco.py b/ppdet/data/source/coco.py
--- a/ppdet/data/source/coco.py
+++ b/ppdet/data/source/coco.py
@@ -146,6 +146,8 @@ def load_roidb_and_cname2cid(self):
                             'x1: {}, y1: {}, x2: {}, y2: {}.'.format(
                                 img_id, x1, y1, x2, y2))
                 num_bbox = len(bboxes)
+                if num_bbox <= 0:
+                    continue
 
                 gt_bbox = np.zeros((num_bbox, 4), dtype=np.float32)
                 gt_class = np.zeros((num_bbox, 1), dtype=np.int32)
@@ -154,6 +156,7 @@ def load_roidb_and_cname2cid(self):
                 difficult = np.zeros((num_bbox, 1), dtype=np.int32)
                 gt_poly = [None] * num_bbox
 
+                has_segmentation = False
                 for i, box in enumerate(bboxes):
                     catid = box['category_id']
                     gt_class[i][0] = catid2clsid[catid]
@@ -161,6 +164,10 @@ def load_roidb_and_cname2cid(self):
                     is_crowd[i][0] = box['iscrowd']
                     if 'segmentation' in box:
                         gt_poly[i] = box['segmentation']
+                        has_segmentation = True
+
+                if has_segmentation and not any(gt_poly):
+                    continue
 
                 coco_rec.update({
                     'is_crowd': is_crowd,


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
coco类型数据自动加上segmention，以至过不了coco的审查
自己生成的coco类型，没有segmentation属性，在运行程序读入时会自动加上，并且是空列表，在/PaddleDetection/ppdet/data/source/coco.py 文件的第169行处通过不了，建议更改为
  `if 'segmentation' in box and bos['segmentation']:`


Discussion:
写错了，是  `if 'segmentation' in box and box['segmentation']:`
@w867066886 收到，感谢反馈

Resolving patch:
diff --git a/dygraph/ppdet/data/source/coco.py b/dygraph/ppdet/data/source/coco.py
--- a/dygraph/ppdet/data/source/coco.py
+++ b/dygraph/ppdet/data/source/coco.py
@@ -128,7 +128,7 @@ def parse_dataset(self, with_background=True):
                     # check RLE format 
                     if 'segmentation' in box and box['iscrowd'] == 1:
                         gt_poly[i] = [[0.0, 0.0], ]
-                    elif 'segmentation' in box:
+                    elif 'segmentation' in box and box['segmentation']:
                         gt_poly[i] = box['segmentation']
                         has_segmentation = True
 
diff --git a/ppdet/data/source/coco.py b/ppdet/data/source/coco.py
--- a/ppdet/data/source/coco.py
+++ b/ppdet/data/source/coco.py
@@ -162,7 +162,7 @@ def load_roidb_and_cname2cid(self):
                     gt_class[i][0] = catid2clsid[catid]
                     gt_bbox[i, :] = box['clean_bbox']
                     is_crowd[i][0] = box['iscrowd']
-                    if 'segmentation' in box:
+                    if 'segmentation' in box and box['segmentation']:
                         gt_poly[i] = box['segmentation']
                         has_segmentation = True
 


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
SOLOV2训练config_solov2 num_class很小时，训练不收敛
https://github.com/PaddlePaddle/PaddleDetection/blob/effbad5db4f1a046dd63cdf14ae49412ba4a836f/ppdet/modeling/anchor_heads/solov2_head.py#L370
cate_score_idx =paddle.add(inds[:, 0] * 80, cate_labels)应该改成下面的写法
cate_score_idx = paddle.add(inds[:, 0] * self.num_classes, cate_labels)
改完后类别数量较小时训练一样收敛


Discussion:

Resolving patch:
diff --git a/ppdet/modeling/heads/solov2_head.py b/ppdet/modeling/heads/solov2_head.py
--- a/ppdet/modeling/heads/solov2_head.py
+++ b/ppdet/modeling/heads/solov2_head.py
@@ -462,7 +462,8 @@ def get_seg_single(self, cate_preds, seg_preds, kernel_preds, featmap_size,
         # cate_labels & kernel_preds
         cate_labels = inds[:, 1]
         kernel_preds = paddle.gather(kernel_preds, index=inds[:, 0])
-        cate_score_idx = paddle.add(inds[:, 0] * 80, cate_labels)
+        cate_score_idx = paddle.add(inds[:, 0] * self.cate_out_channels,
+                                    cate_labels)
         cate_scores = paddle.gather(cate_preds, index=cate_score_idx)
 
         size_trans = np.power(self.seg_num_grids, 2)
diff --git a/static/ppdet/modeling/anchor_heads/solov2_head.py b/static/ppdet/modeling/anchor_heads/solov2_head.py
--- a/static/ppdet/modeling/anchor_heads/solov2_head.py
+++ b/static/ppdet/modeling/anchor_heads/solov2_head.py
@@ -367,8 +367,8 @@ def get_seg_single(self, cate_preds, seg_preds, kernel_preds, featmap_size,
         # cate_labels & kernel_preds
         cate_labels = inds[:, 1]
         kernel_preds = fluid.layers.gather(kernel_preds, index=inds[:, 0])
-        cate_score_idx = fluid.layers.elementwise_add(inds[:, 0] * 80,
-                                                      cate_labels)
+        cate_score_idx = fluid.layers.elementwise_add(
+            inds[:, 0] * self.cate_out_channels, cate_labels)
         cate_scores = fluid.layers.gather(cate_preds, index=cate_score_idx)
 
         size_trans = np.power(self.seg_num_grids, 2)


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
Optimize imports and fix NameError in static/ppdet/modeling/tests/test_architectures.py
This cleanup unnecessary imports and solves the following exception:

![image](https://user-images.githubusercontent.com/31109774/124052497-8746b780-d9ec-11eb-94c8-0a4168d49e8e.png)


Discussion:
@Mandroide Good job, but these modifications have caused bugs, which can be modified according to the CI prompts.
> @Mandroide Good job, but these modifications have caused bugs, which can be modified according to the CI prompts.

What are the CI prompts? I thought I could look at them on a webpage.

Resolving patch:
diff --git a/static/deploy/python/infer.py b/static/deploy/python/infer.py
--- a/static/deploy/python/infer.py
+++ b/static/deploy/python/infer.py
@@ -34,7 +34,7 @@
 import numpy as np
 import paddle
 import paddle.fluid as fluid
-from preprocess import preprocess
+from preprocess import preprocess, Resize, Normalize, Permute, PadStride
 from visualize import visualize_box_mask, lmk2out
 
 # Global dictionary
@@ -507,7 +507,9 @@ def predict_video(detector, camera_id):
     fps = 30
     width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
     height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
+    # yapf: disable
     fourcc = cv2.VideoWriter_fourcc(*'mp4v')
+    # yapf: enable
     if not os.path.exists(FLAGS.output_dir):
         os.makedirs(FLAGS.output_dir)
     out_path = os.path.join(FLAGS.output_dir, video_name)


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
ppdet/data/transform/operators.py程序BUG (Version 2.1.0)
![image](https://user-images.githubusercontent.com/6436061/126308219-739c80aa-7b43-4af9-a870-96b2d23c9de7.png)
该代码有3个BUG:
1、202行：变量名写错了，与参数名称不一致
2、648行：参数名写错了，mask写成了image
3、1273行：没有import情况下，直接使用logging


Discussion:

Resolving patch:
diff --git a/ppdet/data/transform/operators.py b/ppdet/data/transform/operators.py
--- a/ppdet/data/transform/operators.py
+++ b/ppdet/data/transform/operators.py
@@ -33,7 +33,7 @@
 import numpy as np
 import os
 import copy
-
+import logging
 import cv2
 from PIL import Image, ImageDraw
 
@@ -194,7 +194,7 @@ def __init__(self, prob=0.5, lower=0.02, higher=0.4, aspect_ratio=0.3):
         Args:
             prob (float): probability to carry out random erasing
             lower (float): lower limit of the erasing area ratio
-            heigher (float): upper limit of the erasing area ratio
+            higher (float): upper limit of the erasing area ratio
             aspect_ratio (float): aspect ratio of the erasing region
         """
         super(RandomErasingImage, self).__init__()
@@ -646,7 +646,7 @@ def _resize_rle(rle, im_h, im_w, im_scale_x, im_scale_y):
 
             mask = mask_util.decode(rle)
             mask = cv2.resize(
-                image,
+                mask,
                 None,
                 None,
                 fx=im_scale_x,


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
开启print_flops,_flops函数会导致验证跳过第一个batch[BUG]
## 描述问题/Describe the bug
print_flops参数设为True时，eval过程会调用一次self._flops函数，导致验证时跳过第一个batch的数据。
执行到self._eval_with_loader函数(trainer.py, line464)有如下操作：
```
if self.cfg.get('print_flops', False):
    self._flops(loader)
for step_id, data in enumerate(loader):
```
而在self._flops函数中，self.loader,704行有如下取值操作：
```
for data in loader:
        input_data = data
        break
```
此时loader的第一个batch已被取出，这导致 for step_id, data in enumerate(loader)操作的取值是从第2个batch开始的，因此最终的Total Sample num数值始终会少一个batch的数量。batch越大，可能对指标的影响越大。
## 复现/Reproduction

1. 您使用的命令是？/What command or script did you run?

```none
python tools/train.py -c $CONFIG_PATH --eval
```

## 环境/Environment
1. 请提供您使用的Paddle和PaddleDetection的版本号/Please provide the version of Paddle and PaddleDetection you use：

PaddleDetection2.3



Discussion:
@weyoung0 感谢反馈，正在修复

Resolving patch:
diff --git a/ppdet/engine/trainer.py b/ppdet/engine/trainer.py
--- a/ppdet/engine/trainer.py
+++ b/ppdet/engine/trainer.py
@@ -364,7 +364,9 @@ def train(self, validate=False):
         self.status['training_staus'] = stats.TrainingStats(self.cfg.log_iter)
 
         if self.cfg.get('print_flops', False):
-            self._flops(self.loader)
+            flops_loader = create('{}Reader'.format(self.mode.capitalize()))(
+                self.dataset, self.cfg.worker_num)
+            self._flops(flops_loader)
         profiler_options = self.cfg.get('profiler_options', None)
 
         self._compose_callback.on_train_begin(self.status)
@@ -463,7 +465,9 @@ def _eval_with_loader(self, loader):
         self.status['mode'] = 'eval'
         self.model.eval()
         if self.cfg.get('print_flops', False):
-            self._flops(loader)
+            flops_loader = create('{}Reader'.format(self.mode.capitalize()))(
+                self.dataset, self.cfg.worker_num, self._eval_batch_sampler)
+            self._flops(flops_loader)
         for step_id, data in enumerate(loader):
             self.status['step_id'] = step_id
             self._compose_callback.on_step_begin(self.status)
@@ -514,7 +518,8 @@ def predict(self,
         self.status['mode'] = 'test'
         self.model.eval()
         if self.cfg.get('print_flops', False):
-            self._flops(loader)
+            flops_loader = create('TestReader')(self.dataset, 0)
+            self._flops(flops_loader)
         results = []
         for step_id, data in enumerate(loader):
             self.status['step_id'] = step_id


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
PicoFeat use_se 时 assert share_cls_reg == True
https://github.com/PaddlePaddle/PaddleDetection/blob/acaf10684d8e071e48098c660355d54981d93b7c/ppdet/modeling/heads/pico_head.py#L93

```
assert share_cls_reg == True, \
                'In the case of using se, share_cls_reg is not supported'
```

后面的描述和 assert 不符合吧？这里必须 share_cls_reg == True 才能通过。  
但是 picodet_v2.yml 的配置文件中 share_cls_reg: True，use_se: True
搞不懂正确的情况是什么


Discussion:

Resolving patch:
diff --git a/ppdet/modeling/heads/pico_head.py b/ppdet/modeling/heads/pico_head.py
--- a/ppdet/modeling/heads/pico_head.py
+++ b/ppdet/modeling/heads/pico_head.py
@@ -91,7 +91,7 @@ def __init__(self,
         self.reg_convs = []
         if use_se:
             assert share_cls_reg == True, \
-                'In the case of using se, share_cls_reg is not supported'
+                'In the case of using se, share_cls_reg must be set to True'
             self.se = nn.LayerList()
         for stage_idx in range(num_fpn_stride):
             cls_subnet_convs = []


Test patch:
Repository: PaddlePaddle/PaddleDetection

Issue:
训练出现长警告
### 问题确认 Search before asking

- [X] 我已经查询[历史issue](https://github.com/PaddlePaddle/PaddleDetection/issues)，没有发现相似的bug。I have searched the [issues](https://github.com/PaddlePaddle/PaddleDetection/issues) and found no similar bug report.


### Bug组件 Bug Component

_No response_

### Bug描述 Describe the Bug

训练出现长警告
```
I0706 13:09:13.075042 3772 eager_method.cc:140] Warning:: 0D Tensor cannot be used as 'Tensor.numpy()[0]' . In order to avoid this problem, 0D Tensor will be changed to 1D numpy currently, but it's not correct and will be removed in release 2.6. For Tensor contain only one element, Please modify 'Tensor.numpy()[0]' to 'float(Tensor)' as soon as possible, otherwise 'Tensor.numpy()[0]' will raise error in release 2.6.
I0706 13:09:13.382442 3772 eager_method.cc:140] Warning:: 0D Tensor cannot be used as 'Tensor.numpy()[0]' . In order to avoid this problem, 0D Tensor will be changed to 1D numpy currently, but it's not correct and will be removed in release 2.6. For Tensor contain only one element, Please modify 'Tensor.numpy()[0]' to 'float(Tensor)' as soon as possible, otherwise 'Tensor.numpy()[0]' will raise error in release 2.6.
```

### 复现环境 Environment

PaddleDetection2.6
PaddlePaddle2.5.0

经过排查将`ppdet/utils/stats.py`第77行进行如下修改
`v.update(stats[k].numpy())`→`v.update(float(stats[k]))`

### Bug描述确认 Bug description confirmation

- [X] 我确认已经提供了Bug复现步骤、代码改动说明、以及环境信息，确认问题是可以复现的。I confirm that the bug replication steps, code change instructions, and environment information have been provided, and the problem can be reproduced.


### 是否愿意提交PR？ Are you willing to submit a PR?

- [ ] 我愿意提交PR！I'd like to help by submitting a PR!


Discussion:
感谢反馈，会尽快修复

Resolving patch:
diff --git a/ppdet/utils/stats.py b/ppdet/utils/stats.py
--- a/ppdet/utils/stats.py
+++ b/ppdet/utils/stats.py
@@ -74,7 +74,7 @@ def update(self, stats):
                 for k in stats.keys()
             }
         for k, v in self.meters.items():
-            v.update(stats[k].numpy())
+            v.update(float(stats[k]))
 
     def get(self, extras=None):
         stats = collections.OrderedDict()


Test patch:
Repository: pypi/warehouse

Issue:
Redirect a slash-less URL to the slashed variant
We have urls like `/project/foobar/`, if someone enters `/project/foobar` we should redirect that to `/project/foobar/`.



Discussion:

Resolving patch:
diff --git a/warehouse/config.py b/warehouse/config.py
--- a/warehouse/config.py
+++ b/warehouse/config.py
@@ -124,6 +124,10 @@ def configure(settings=None):
     })
     config.add_tween("warehouse.config.content_security_policy_tween_factory")
 
+    # If a route matches with a slash appended to it, redirect to that route
+    # instead of returning a HTTPNotFound.
+    config.add_notfound_view(append_slash=True)
+
     # Configure the filesystems we use.
     config.registry["filesystems"] = {}
     for key, path in {


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -96,6 +96,7 @@ def __init__(self):
             lambda d: configurator_settings.update(d)
         ),
         add_tween=pretend.call_recorder(lambda tween_factory: None),
+        add_notfound_view=pretend.call_recorder(lambda append_slash: None),
         add_static_view=pretend.call_recorder(
             lambda name, path, cachebust: None
         ),
@@ -191,3 +192,6 @@ def __init__(self):
     assert opener.calls == [
         pretend.call("/srv/data/pypi/packages/", create_dir=True),
     ]
+    assert configurator_obj.add_notfound_view.calls == [
+        pretend.call(append_slash=True),
+    ]
Repository: pypi/warehouse

Issue:
Prevent Race Conditions by Utilizing Transaction Isolation
We need to ensure that we don't get non-repeatable reads and phantom reads on some of the API endpoints. In particular anything that Bandersnatch is going to use that has a serial number we need to make sure that nothing changes between the query that gets the serial number and the quer(y|ies) that get the actual data. 

Implement a read only route predicate
A possible solution to #405, see that issue for discussion.

Fixes #405
Closes #436



Discussion:

Resolving patch:
diff --git a/warehouse/db.py b/warehouse/db.py
--- a/warehouse/db.py
+++ b/warehouse/db.py
@@ -82,6 +82,7 @@ def includeme(config):
     # Create our SQLAlchemy Engine.
     config.registry["sqlalchemy.engine"] = sqlalchemy.create_engine(
         config.registry.settings["database.url"],
+        isolation_level="SERIALIZABLE",
     )
 
     # Register our request.db property


Test patch:
diff --git a/tests/test_db.py b/tests/test_db.py
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -82,7 +82,7 @@ class FakeRegistry(dict):
         settings = {"database.url": pretend.stub()}
 
     engine = pretend.stub()
-    create_engine = pretend.call_recorder(lambda url: engine)
+    create_engine = pretend.call_recorder(lambda url, isolation_level: engine)
     config = pretend.stub(
         add_directive=pretend.call_recorder(lambda *a: None),
         registry=FakeRegistry(),
@@ -97,7 +97,10 @@ class FakeRegistry(dict):
         pretend.call("alembic_config", _configure_alembic),
     ]
     assert create_engine.calls == [
-        pretend.call(config.registry.settings["database.url"]),
+        pretend.call(
+            config.registry.settings["database.url"],
+            isolation_level="SERIALIZABLE",
+        ),
     ]
     assert config.registry["sqlalchemy.engine"] is engine
     assert config.add_request_method.calls == [
Repository: pypi/warehouse

Issue:
Remove the precomputed description_html from the DB
We don't really need to do this. It makes it harder to ever adjust how we render the pages and overall caching is a better method of dealing with any sort of expensiveness in rendering.



Discussion:

Resolving patch:
diff --git a/warehouse/migrations/versions/41e9207fbe5_remove_the_description_html_column.py b/warehouse/migrations/versions/41e9207fbe5_remove_the_description_html_column.py
new file mode 100644
--- /dev/null
+++ b/warehouse/migrations/versions/41e9207fbe5_remove_the_description_html_column.py
@@ -0,0 +1,32 @@
+# 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.
+"""
+Remove the description_html column
+
+Revision ID: 41e9207fbe5
+Revises: 49b93c346db
+Create Date: 2015-06-03 19:44:43.269987
+"""
+
+from alembic import op
+
+
+revision = "41e9207fbe5"
+down_revision = "49b93c346db"
+
+
+def upgrade():
+    op.drop_column("releases", "description_html")
+
+
+def downgrade():
+    raise RuntimeError("Cannot downgrade past {!r}".format(revision))
diff --git a/warehouse/packaging/models.py b/warehouse/packaging/models.py
--- a/warehouse/packaging/models.py
+++ b/warehouse/packaging/models.py
@@ -152,7 +152,6 @@ def __table_args__(cls):  # noqa
     download_url = Column(Text)
     _pypi_ordering = Column(Integer)
     _pypi_hidden = Column(Boolean)
-    description_html = Column(Text)
     cheesecake_installability_id = Column(
         Integer,
         ForeignKey("cheesecake_main_indices.id"),


Test patch:
Repository: pypi/warehouse

Issue:
Use the latest version of pip-tools



Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -76,6 +76,7 @@
         "hiredis",
         "html5lib",
         "itsdangerous",
+        "Jinja2>=2.8",
         "msgpack-python",
         "passlib>=1.6.4",
         "psycopg2",
@@ -91,6 +92,7 @@
         "sqlalchemy-citext",
         "structlog",
         "transaction",
+        "WebOb>=1.5.0a0",
         "WTForms>=2.0.0",
         "zope.sqlalchemy",
     ],
diff --git a/tasks/pip.py b/tasks/pip.py
--- a/tasks/pip.py
+++ b/tasks/pip.py
@@ -24,8 +24,6 @@
 #    $ invoke pip.compile
 #
 
--f https://github.com/Pylons/webob/archive/master.zip#egg=webob-1.5.dev0
-
 """.lstrip()
 
 
@@ -42,8 +40,6 @@ def compile():
     lines = [REQUIREMENTS_HEADER]
     with open("requirements.txt", "r") as fp:
         for line in fp:
-            line = re.sub(r"^webob==(\S+)(.*)$", r"webob==1.5.dev0\2", line)
-
             # The boto3 wheel includes a futures==2.2.0 even though that is a
             # Python 2 only dependency. This dependency comes by default on
             # Python 3, so the backport is never needed. See boto/boto3#163.


Test patch:
Repository: pypi/warehouse

Issue:
Support CORS on JSON API
Say I want to show project stats like monthly downloads on the project page. Fetching this from client side JavaScript allows me to retain a completely static project page (can be hosted on a CDN).

However, the JSON API does not set `Access-Control-Allow-Origin` header - no CORS support.

Test:
https://github.com/oberstet/scratchbox/blob/master/js/badges/pypi.html

For comparison, this is what GitHub does with it's API:
https://github.com/oberstet/scratchbox/blob/master/js/badges/github.html

If above would work, that makes #330 void (for me) - if the API doesn't get rate limited or such ..



Discussion:
Still fails for me: "XMLHttpRequest cannot load https://pypi.python.org/PyPI/autobahn/json. Request header field Content-type is not allowed by Access-Control-Allow-Headers."

Hrmph. Ok

I'll add more headers to that then :)

Thanks!

The HTML here https://github.com/oberstet/scratchbox/blob/master/js/badges/pypi.html demonstrates the issue ..

I took a look at your file, and if I edited out `req.setRequestHeader("Content-type", "application/json; charset=utf-8");` it worked fine. You're not sending a body along so there's no real reason to set a `Content-Type` on the request is there?


Resolving patch:
diff --git a/warehouse/legacy/api/json.py b/warehouse/legacy/api/json.py
--- a/warehouse/legacy/api/json.py
+++ b/warehouse/legacy/api/json.py
@@ -71,6 +71,18 @@ def json_release(release, request):
 
     # We want to allow CORS here to enable anyone to fetch data from this API
     request.response.headers["Access-Control-Allow-Origin"] = "*"
+    request.response.headers["Access-Control-Allow-Headers"] = ", ".join([
+        "Content-Type",
+        "If-Match",
+        "If-Modified-Since",
+        "If-None-Match",
+        "If-Unmodified-Since",
+    ])
+    request.response.headers["Access-Control-Allow-Methods"] = "GET"
+    request.response.headers["Access-Control-Max-Age"] = "86400"
+    request.response.headers["Access-Control-Expose-Headers"] = ", ".join([
+        "X-PyPI-Last-Serial",
+    ])
 
     # Get the latest serial number for this project.
     serial = (


Test patch:
diff --git a/tests/unit/legacy/api/test_json.py b/tests/unit/legacy/api/test_json.py
--- a/tests/unit/legacy/api/test_json.py
+++ b/tests/unit/legacy/api/test_json.py
@@ -141,6 +141,13 @@ def test_detail_renders(self, pyramid_config, db_request):
 
         headers = db_request.response.headers
         assert headers["Access-Control-Allow-Origin"] == "*"
+        assert headers["Access-Control-Allow-Headers"] == (
+            "Content-Type, If-Match, If-Modified-Since, If-None-Match, "
+            "If-Unmodified-Since"
+        )
+        assert headers["Access-Control-Allow-Methods"] == "GET"
+        assert headers["Access-Control-Max-Age"] == "86400"
+        assert headers["Access-Control-Expose-Headers"] == "X-PyPI-Last-Serial"
         assert headers["X-PyPI-Last-Serial"] == je.id
 
         assert result == {