prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Vulnerability reporting task is not resilient to deleted releases
  Currently attempting to publish a vulnerability report for a notification that includes release versions that have been deleted (or never existed) causes an error and prevents the entire report from being stored.
  
  Example: https://sentry.io/share/issue/69362937025d4524be01918a80f830d1/
  
  If the release is not found, we should just skip that version and continue storing the vulnerability report. If *none* of the versions are found, we should error.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `warehouse/integrations/vulnerabilities/utils.py`
  Content:
  ```
  1 # Licensed under the Apache License, Version 2.0 (the "License");
  2 # you may not use this file except in compliance with the License.
  3 # You may obtain a copy of the License at
  4 #
  5 # http://www.apache.org/licenses/LICENSE-2.0
  6 #
  7 # Unless required by applicable law or agreed to in writing, software
  8 # distributed under the License is distributed on an "AS IS" BASIS,
  9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 # See the License for the specific language governing permissions and
  11 # limitations under the License.
  12 
  13 
  14 from sqlalchemy import func, orm
  15 from sqlalchemy.orm.exc import NoResultFound
  16 
  17 from warehouse.integrations import vulnerabilities
  18 from warehouse.integrations.vulnerabilities.models import VulnerabilityRecord
  19 from warehouse.packaging.models import Project, Release
  20 
  21 
  22 def _get_project(request, vuln_report: vulnerabilities.VulnerabilityReportRequest):
  23     return (
  24         request.db.query(Project)
  25         .filter(
  26             Project.normalized_name == func.normalize_pep426_name(vuln_report.project)
  27         )
  28         .one()
  29     )
  30 
  31 
  32 def _get_release(request, project: Project, version):
  33     return (
  34         request.db.query(Release)
  35         .filter(Release.project_id == project.id)
  36         .filter(Release.version == version)
  37         .one()
  38     )
  39 
  40 
  41 def _get_vuln_record(
  42     request, vuln_report: vulnerabilities.VulnerabilityReportRequest, origin
  43 ):
  44     return (
  45         request.db.query(VulnerabilityRecord)
  46         .filter(VulnerabilityRecord.id == vuln_report.vulnerability_id)
  47         .filter(VulnerabilityRecord.source == origin)
  48         .options(orm.joinedload(VulnerabilityRecord.releases))
  49         .one()
  50     )
  51 
  52 
  53 def _delete_vuln_record(request, vuln_record: VulnerabilityRecord):
  54     request.db.delete(vuln_record)
  55 
  56 
  57 def _add_vuln_record(request, vuln_record: VulnerabilityRecord):
  58     request.db.add(vuln_record)
  59 
  60 
  61 def _analyze_vulnerability(request, vulnerability_report, origin, metrics):
  62     try:
  63         report = vulnerabilities.VulnerabilityReportRequest.from_api_request(
  64             request=vulnerability_report
  65         )
  66     except vulnerabilities.InvalidVulnerabilityReportRequest as exc:
  67         metrics.increment(
  68             f"warehouse.vulnerabilities.error.{exc.reason}", tags=[f"origin:{origin}"]
  69         )
  70         raise
  71 
  72     metrics.increment("warehouse.vulnerabilities.valid", tags=[f"origin:{origin}"])
  73 
  74     try:
  75         vulnerability_record = _get_vuln_record(request, report, origin)
  76 
  77         if not report.versions:
  78             # No versions indicates the vulnerability is no longer considered
  79             # valid, so delete it.
  80             _delete_vuln_record(request, vulnerability_record)
  81             return
  82 
  83     except NoResultFound:
  84         if not report.versions:
  85             return
  86 
  87         vulnerability_record = VulnerabilityRecord(
  88             id=report.vulnerability_id,
  89             source=origin,
  90             link=report.advisory_link,
  91             aliases=report.aliases,
  92         )
  93         _add_vuln_record(request, vulnerability_record)
  94 
  95     try:
  96         project = _get_project(request, report)
  97     except NoResultFound:
  98         metrics.increment(
  99             "warehouse.vulnerabilities.error.project_not_found",
  100             tags=[f"origin:{origin}"],
  101         )
  102         raise
  103 
  104     for version in report.versions:
  105         try:
  106             release = _get_release(request, project, version)
  107         except NoResultFound:
  108             metrics.increment(
  109                 "warehouse.vulnerabilities.error.release_not_found",
  110                 tags=[f"origin:{origin}"],
  111             )
  112             raise
  113 
  114         if release not in vulnerability_record.releases:
  115             vulnerability_record.releases.append(release)
  116 
  117     # Unassociate any releases that no longer apply.
  118     for release in list(vulnerability_record.releases):
  119         if release.version not in report.versions:
  120             vulnerability_record.releases.remove(release)
  121 
  122 
  123 def analyze_vulnerability(request, vulnerability_report, origin, metrics):
  124     metrics.increment("warehouse.vulnerabilities.received", tags=[f"origin:{origin}"])
  125     try:
  126         with metrics.timed(
  127             "warehouse.vulnerabilities.analysis", tags=[f"origin:{origin}"]
  128         ):
  129             _analyze_vulnerability(
  130                 request=request,
  131                 vulnerability_report=vulnerability_report,
  132                 origin=origin,
  133                 metrics=metrics,
  134             )
  135 
  136         metrics.increment(
  137             "warehouse.vulnerabilities.processed", tags=[f"origin:{origin}"]
  138         )
  139     except (vulnerabilities.InvalidVulnerabilityReportRequest, NoResultFound):
  140         raise
  141     except Exception:
  142         metrics.increment(
  143             "warehouse.vulnerabilities.error.unknown", tags=[f"origin:{origin}"]
  144         )
  145         raise
  146 
  147 
  148 def analyze_vulnerabilities(request, vulnerability_reports, origin, metrics):
  149     from warehouse.integrations.vulnerabilities import tasks
  150 
  151     if not isinstance(vulnerability_reports, list):
  152         metrics.increment(
  153             "warehouse.vulnerabilities.error.format", tags=[f"origin:{origin}"]
  154         )
  155         raise vulnerabilities.InvalidVulnerabilityReportRequest(
  156             "Invalid format: payload is not a list", "format"
  157         )
  158 
  159     for vulnerability_report in vulnerability_reports:
  160         request.task(tasks.analyze_vulnerability_task).delay(
  161             vulnerability_report=vulnerability_report,
  162             origin=origin,
  163         )
  164
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/warehouse/integrations/vulnerabilities/utils.py b/warehouse/integrations/vulnerabilities/utils.py
  --- a/warehouse/integrations/vulnerabilities/utils.py
  +++ b/warehouse/integrations/vulnerabilities/utils.py
  @@ -10,7 +10,7 @@
   # See the License for the specific language governing permissions and
   # limitations under the License.
   
  -
  +from pyramid.httpexceptions import HTTPBadRequest
   from sqlalchemy import func, orm
   from sqlalchemy.orm.exc import NoResultFound
   
  @@ -101,19 +101,26 @@
           )
           raise
   
  +    found_releases = False  # by now, we don't have any release found
  +
       for version in report.versions:
           try:
               release = _get_release(request, project, version)
  +            found_releases = True  # at least one release found
           except NoResultFound:
               metrics.increment(
                   "warehouse.vulnerabilities.error.release_not_found",
                   tags=[f"origin:{origin}"],
               )
  -            raise
  +            continue  # skip that release
   
           if release not in vulnerability_record.releases:
               vulnerability_record.releases.append(release)
   
  +    if not found_releases:
  +        # no releases found, then raise an exception
  +        raise HTTPBadRequest("None of the releases were found")
  +
       # Unassociate any releases that no longer apply.
       for release in list(vulnerability_record.releases):
           if release.version not in report.versions:
  @@ -136,7 +143,11 @@
           metrics.increment(
               "warehouse.vulnerabilities.processed", tags=[f"origin:{origin}"]
           )
  -    except (vulnerabilities.InvalidVulnerabilityReportRequest, NoResultFound):
  +    except (
  +        vulnerabilities.InvalidVulnerabilityReportRequest,
  +        NoResultFound,
  +        HTTPBadRequest,
  +    ):
           raise
       except Exception:
           metrics.increment(
  
verification_info: {"golden_diff": "diff --git a/warehouse/integrations/vulnerabilities/utils.py b/warehouse/integrations/vulnerabilities/utils.py\n--- a/warehouse/integrations/vulnerabilities/utils.py\n+++ b/warehouse/integrations/vulnerabilities/utils.py\n@@ -10,7 +10,7 @@\n # See the License for the specific language governing permissions and\n # limitations under the License.\n \n-\n+from pyramid.httpexceptions import HTTPBadRequest\n from sqlalchemy import func, orm\n from sqlalchemy.orm.exc import NoResultFound\n \n@@ -101,19 +101,26 @@\n         )\n         raise\n \n+    found_releases = False  # by now, we don't have any release found\n+\n     for version in report.versions:\n         try:\n             release = _get_release(request, project, version)\n+            found_releases = True  # at least one release found\n         except NoResultFound:\n             metrics.increment(\n                 \"warehouse.vulnerabilities.error.release_not_found\",\n                 tags=[f\"origin:{origin}\"],\n             )\n-            raise\n+            continue  # skip that release\n \n         if release not in vulnerability_record.releases:\n             vulnerability_record.releases.append(release)\n \n+    if not found_releases:\n+        # no releases found, then raise an exception\n+        raise HTTPBadRequest(\"None of the releases were found\")\n+\n     # Unassociate any releases that no longer apply.\n     for release in list(vulnerability_record.releases):\n         if release.version not in report.versions:\n@@ -136,7 +143,11 @@\n         metrics.increment(\n             \"warehouse.vulnerabilities.processed\", tags=[f\"origin:{origin}\"]\n         )\n-    except (vulnerabilities.InvalidVulnerabilityReportRequest, NoResultFound):\n+    except (\n+        vulnerabilities.InvalidVulnerabilityReportRequest,\n+        NoResultFound,\n+        HTTPBadRequest,\n+    ):\n         raise\n     except Exception:\n         metrics.increment(\n", "issue": "Vulnerability reporting task is not resilient to deleted releases\nCurrently attempting to publish a vulnerability report for a notification that includes release versions that have been deleted (or never existed) causes an error and prevents the entire report from being stored.\r\n\r\nExample: https://sentry.io/share/issue/69362937025d4524be01918a80f830d1/\r\n\r\nIf the release is not found, we should just skip that version and continue storing the vulnerability report. If *none* of the versions are found, we should error.\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nfrom sqlalchemy import func, orm\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.integrations import vulnerabilities\nfrom warehouse.integrations.vulnerabilities.models import VulnerabilityRecord\nfrom warehouse.packaging.models import Project, Release\n\n\ndef _get_project(request, vuln_report: vulnerabilities.VulnerabilityReportRequest):\n    return (\n        request.db.query(Project)\n        .filter(\n            Project.normalized_name == func.normalize_pep426_name(vuln_report.project)\n        )\n        .one()\n    )\n\n\ndef _get_release(request, project: Project, version):\n    return (\n        request.db.query(Release)\n        .filter(Release.project_id == project.id)\n        .filter(Release.version == version)\n        .one()\n    )\n\n\ndef _get_vuln_record(\n    request, vuln_report: vulnerabilities.VulnerabilityReportRequest, origin\n):\n    return (\n        request.db.query(VulnerabilityRecord)\n        .filter(VulnerabilityRecord.id == vuln_report.vulnerability_id)\n        .filter(VulnerabilityRecord.source == origin)\n        .options(orm.joinedload(VulnerabilityRecord.releases))\n        .one()\n    )\n\n\ndef _delete_vuln_record(request, vuln_record: VulnerabilityRecord):\n    request.db.delete(vuln_record)\n\n\ndef _add_vuln_record(request, vuln_record: VulnerabilityRecord):\n    request.db.add(vuln_record)\n\n\ndef _analyze_vulnerability(request, vulnerability_report, origin, metrics):\n    try:\n        report = vulnerabilities.VulnerabilityReportRequest.from_api_request(\n            request=vulnerability_report\n        )\n    except vulnerabilities.InvalidVulnerabilityReportRequest as exc:\n        metrics.increment(\n            f\"warehouse.vulnerabilities.error.{exc.reason}\", tags=[f\"origin:{origin}\"]\n        )\n        raise\n\n    metrics.increment(\"warehouse.vulnerabilities.valid\", tags=[f\"origin:{origin}\"])\n\n    try:\n        vulnerability_record = _get_vuln_record(request, report, origin)\n\n        if not report.versions:\n            # No versions indicates the vulnerability is no longer considered\n            # valid, so delete it.\n            _delete_vuln_record(request, vulnerability_record)\n            return\n\n    except NoResultFound:\n        if not report.versions:\n            return\n\n        vulnerability_record = VulnerabilityRecord(\n            id=report.vulnerability_id,\n            source=origin,\n            link=report.advisory_link,\n            aliases=report.aliases,\n        )\n        _add_vuln_record(request, vulnerability_record)\n\n    try:\n        project = _get_project(request, report)\n    except NoResultFound:\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.project_not_found\",\n            tags=[f\"origin:{origin}\"],\n        )\n        raise\n\n    for version in report.versions:\n        try:\n            release = _get_release(request, project, version)\n        except NoResultFound:\n            metrics.increment(\n                \"warehouse.vulnerabilities.error.release_not_found\",\n                tags=[f\"origin:{origin}\"],\n            )\n            raise\n\n        if release not in vulnerability_record.releases:\n            vulnerability_record.releases.append(release)\n\n    # Unassociate any releases that no longer apply.\n    for release in list(vulnerability_record.releases):\n        if release.version not in report.versions:\n            vulnerability_record.releases.remove(release)\n\n\ndef analyze_vulnerability(request, vulnerability_report, origin, metrics):\n    metrics.increment(\"warehouse.vulnerabilities.received\", tags=[f\"origin:{origin}\"])\n    try:\n        with metrics.timed(\n            \"warehouse.vulnerabilities.analysis\", tags=[f\"origin:{origin}\"]\n        ):\n            _analyze_vulnerability(\n                request=request,\n                vulnerability_report=vulnerability_report,\n                origin=origin,\n                metrics=metrics,\n            )\n\n        metrics.increment(\n            \"warehouse.vulnerabilities.processed\", tags=[f\"origin:{origin}\"]\n        )\n    except (vulnerabilities.InvalidVulnerabilityReportRequest, NoResultFound):\n        raise\n    except Exception:\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.unknown\", tags=[f\"origin:{origin}\"]\n        )\n        raise\n\n\ndef analyze_vulnerabilities(request, vulnerability_reports, origin, metrics):\n    from warehouse.integrations.vulnerabilities import tasks\n\n    if not isinstance(vulnerability_reports, list):\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.format\", tags=[f\"origin:{origin}\"]\n        )\n        raise vulnerabilities.InvalidVulnerabilityReportRequest(\n            \"Invalid format: payload is not a list\", \"format\"\n        )\n\n    for vulnerability_report in vulnerability_reports:\n        request.task(tasks.analyze_vulnerability_task).delay(\n            vulnerability_report=vulnerability_report,\n            origin=origin,\n        )\n", "path": "warehouse/integrations/vulnerabilities/utils.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pyramid.httpexceptions import HTTPBadRequest\nfrom sqlalchemy import func, orm\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.integrations import vulnerabilities\nfrom warehouse.integrations.vulnerabilities.models import VulnerabilityRecord\nfrom warehouse.packaging.models import Project, Release\n\n\ndef _get_project(request, vuln_report: vulnerabilities.VulnerabilityReportRequest):\n    return (\n        request.db.query(Project)\n        .filter(\n            Project.normalized_name == func.normalize_pep426_name(vuln_report.project)\n        )\n        .one()\n    )\n\n\ndef _get_release(request, project: Project, version):\n    return (\n        request.db.query(Release)\n        .filter(Release.project_id == project.id)\n        .filter(Release.version == version)\n        .one()\n    )\n\n\ndef _get_vuln_record(\n    request, vuln_report: vulnerabilities.VulnerabilityReportRequest, origin\n):\n    return (\n        request.db.query(VulnerabilityRecord)\n        .filter(VulnerabilityRecord.id == vuln_report.vulnerability_id)\n        .filter(VulnerabilityRecord.source == origin)\n        .options(orm.joinedload(VulnerabilityRecord.releases))\n        .one()\n    )\n\n\ndef _delete_vuln_record(request, vuln_record: VulnerabilityRecord):\n    request.db.delete(vuln_record)\n\n\ndef _add_vuln_record(request, vuln_record: VulnerabilityRecord):\n    request.db.add(vuln_record)\n\n\ndef _analyze_vulnerability(request, vulnerability_report, origin, metrics):\n    try:\n        report = vulnerabilities.VulnerabilityReportRequest.from_api_request(\n            request=vulnerability_report\n        )\n    except vulnerabilities.InvalidVulnerabilityReportRequest as exc:\n        metrics.increment(\n            f\"warehouse.vulnerabilities.error.{exc.reason}\", tags=[f\"origin:{origin}\"]\n        )\n        raise\n\n    metrics.increment(\"warehouse.vulnerabilities.valid\", tags=[f\"origin:{origin}\"])\n\n    try:\n        vulnerability_record = _get_vuln_record(request, report, origin)\n\n        if not report.versions:\n            # No versions indicates the vulnerability is no longer considered\n            # valid, so delete it.\n            _delete_vuln_record(request, vulnerability_record)\n            return\n\n    except NoResultFound:\n        if not report.versions:\n            return\n\n        vulnerability_record = VulnerabilityRecord(\n            id=report.vulnerability_id,\n            source=origin,\n            link=report.advisory_link,\n            aliases=report.aliases,\n        )\n        _add_vuln_record(request, vulnerability_record)\n\n    try:\n        project = _get_project(request, report)\n    except NoResultFound:\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.project_not_found\",\n            tags=[f\"origin:{origin}\"],\n        )\n        raise\n\n    found_releases = False  # by now, we don't have any release found\n\n    for version in report.versions:\n        try:\n            release = _get_release(request, project, version)\n            found_releases = True  # at least one release found\n        except NoResultFound:\n            metrics.increment(\n                \"warehouse.vulnerabilities.error.release_not_found\",\n                tags=[f\"origin:{origin}\"],\n            )\n            continue  # skip that release\n\n        if release not in vulnerability_record.releases:\n            vulnerability_record.releases.append(release)\n\n    if not found_releases:\n        # no releases found, then raise an exception\n        raise HTTPBadRequest(\"None of the releases were found\")\n\n    # Unassociate any releases that no longer apply.\n    for release in list(vulnerability_record.releases):\n        if release.version not in report.versions:\n            vulnerability_record.releases.remove(release)\n\n\ndef analyze_vulnerability(request, vulnerability_report, origin, metrics):\n    metrics.increment(\"warehouse.vulnerabilities.received\", tags=[f\"origin:{origin}\"])\n    try:\n        with metrics.timed(\n            \"warehouse.vulnerabilities.analysis\", tags=[f\"origin:{origin}\"]\n        ):\n            _analyze_vulnerability(\n                request=request,\n                vulnerability_report=vulnerability_report,\n                origin=origin,\n                metrics=metrics,\n            )\n\n        metrics.increment(\n            \"warehouse.vulnerabilities.processed\", tags=[f\"origin:{origin}\"]\n        )\n    except (\n        vulnerabilities.InvalidVulnerabilityReportRequest,\n        NoResultFound,\n        HTTPBadRequest,\n    ):\n        raise\n    except Exception:\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.unknown\", tags=[f\"origin:{origin}\"]\n        )\n        raise\n\n\ndef analyze_vulnerabilities(request, vulnerability_reports, origin, metrics):\n    from warehouse.integrations.vulnerabilities import tasks\n\n    if not isinstance(vulnerability_reports, list):\n        metrics.increment(\n            \"warehouse.vulnerabilities.error.format\", tags=[f\"origin:{origin}\"]\n        )\n        raise vulnerabilities.InvalidVulnerabilityReportRequest(\n            \"Invalid format: payload is not a list\", \"format\"\n        )\n\n    for vulnerability_report in vulnerability_reports:\n        request.task(tasks.analyze_vulnerability_task).delay(\n            vulnerability_report=vulnerability_report,\n            origin=origin,\n        )\n", "path": "warehouse/integrations/vulnerabilities/utils.py"}]}