get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/131237/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 131237,
    "url": "https://patches.dpdk.org/api/patches/131237/?format=api",
    "web_url": "https://patches.dpdk.org/project/ci/patch/20230907205551.19066-3-jspewock@iol.unh.edu/",
    "project": {
        "id": 5,
        "url": "https://patches.dpdk.org/api/projects/5/?format=api",
        "name": "CI",
        "link_name": "ci",
        "list_id": "ci.dpdk.org",
        "list_email": "ci@dpdk.org",
        "web_url": "",
        "scm_url": "git://dpdk.org/tools/dpdk-ci",
        "webscm_url": "https://git.dpdk.org/tools/dpdk-ci/",
        "list_archive_url": "https://inbox.dpdk.org/ci",
        "list_archive_url_format": "https://inbox.dpdk.org/ci/{}",
        "commit_url_format": ""
    },
    "msgid": "<20230907205551.19066-3-jspewock@iol.unh.edu>",
    "list_archive_url": "https://inbox.dpdk.org/ci/20230907205551.19066-3-jspewock@iol.unh.edu",
    "date": "2023-09-07T20:45:55",
    "name": "[v2,1/1] tools: add get_reruns script",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "93fd36601596d4f01e11b2acb04bf644f1ead2f3",
    "submitter": {
        "id": 2772,
        "url": "https://patches.dpdk.org/api/people/2772/?format=api",
        "name": "Jeremy Spewock",
        "email": "jspewock@iol.unh.edu"
    },
    "delegate": null,
    "mbox": "https://patches.dpdk.org/project/ci/patch/20230907205551.19066-3-jspewock@iol.unh.edu/mbox/",
    "series": [
        {
            "id": 29452,
            "url": "https://patches.dpdk.org/api/series/29452/?format=api",
            "web_url": "https://patches.dpdk.org/project/ci/list/?series=29452",
            "date": "2023-09-07T20:45:54",
            "name": "tools: Add script for getting rerun requests",
            "version": 2,
            "mbox": "https://patches.dpdk.org/series/29452/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/131237/comments/",
    "check": "pending",
    "checks": "https://patches.dpdk.org/api/patches/131237/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<ci-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 84A054253B;\n\tThu,  7 Sep 2023 22:56:01 +0200 (CEST)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 3C75D402BC;\n\tThu,  7 Sep 2023 22:56:01 +0200 (CEST)",
            "from mail-il1-f228.google.com (mail-il1-f228.google.com\n [209.85.166.228])\n by mails.dpdk.org (Postfix) with ESMTP id 44AAE400EF\n for <ci@dpdk.org>; Thu,  7 Sep 2023 22:56:00 +0200 (CEST)",
            "by mail-il1-f228.google.com with SMTP id\n e9e14a558f8ab-34ca1aadcccso5775805ab.3\n for <ci@dpdk.org>; Thu, 07 Sep 2023 13:56:00 -0700 (PDT)",
            "from postal.iol.unh.edu (postal.iol.unh.edu. [132.177.123.84])\n by smtp-relay.gmail.com with ESMTPS id\n h5-20020a02c725000000b00435ab9a78f5sm4285jao.42.2023.09.07.13.55.59\n (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);\n Thu, 07 Sep 2023 13:55:59 -0700 (PDT)",
            "from iol.unh.edu (unknown\n [IPv6:2606:4100:3880:1220:58fe:317e:37bd:a524])\n by postal.iol.unh.edu (Postfix) with ESMTP id 105E66052471;\n Thu,  7 Sep 2023 16:55:59 -0400 (EDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=iol.unh.edu; s=unh-iol; t=1694120159; x=1694724959; darn=dpdk.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=Go5+FOlAV9MYqBVV3rvdWaHwzdBtTaTSFCc6U9XYZsQ=;\n b=dSMefLKNktIJDMy8gy2OiNlooeiZn+IiZNRvuWZchkPgoy0PPaQ9iwLs5Q6jNDrdbW\n zboB9i5LI5L0mMEz0lopJe8j/1GfcVNg/+u/wiw8+I+BUnCc5iZBuGhB9RCD6NFKZGgY\n 1MSKxNXu29vgblFjCYVn7YoQl/53JKlutz9pk=",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1694120159; x=1694724959;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=Go5+FOlAV9MYqBVV3rvdWaHwzdBtTaTSFCc6U9XYZsQ=;\n b=b2gx+IJLLXspQaeAc5Ny3gGwcmT/1uIvqLSBogCtffMrDkEXnEZ+sLXrjfwPz5T5iF\n k90LiezFBz2rHPnFOEf6RyJn+jZxEXrDAqs29GbVKYTz5lNp+Vpnn8RTPO/5W5syjCYu\n x8XQPBALv1Jt0S2CJ2wKi03jv99EcyViK06CrCigChUm7S05SllepAfbXW6yd8WZB/uV\n eVZ6SK9xifJpSBvowvjhVXCKwICN3nK7pRJCKVoW3kyxaT+NwwJzaUwSt0zlkS55A4oO\n e7FbbVPEU06QMJXEVSi/mBPV7pA0wG3z/pLdN6UR/9NUQpOpPDP8pgmdELZIQ3xexZY5\n TZuQ==",
        "X-Gm-Message-State": "AOJu0YxWNg552FH8UxVhdh4Xc9qszBCflRPYKxNzLH7oPmJCTK+X/C+i\n Rt7jWHsxKMOJ6KmHz6NGgraAKSLoCNJCSi7e3VE9vB2vN+jozHNY1BHjU4oBXFbpGnhFzvDQqbF\n CIRsn0go44RmO6pobWnYEkPuu3s7xm77dnkdqDU0oh/Qpclvb30fUpWPJJ44OEzK3zCFLwoNlau\n J2/6mo",
        "X-Google-Smtp-Source": "\n AGHT+IEoIDtqKzWT5xKe7ptXhd9vS+h5SlixMw8CRH5rgVQwm/9gxYUHfSkNxA7lwwUzlK7rP89ucLhhsp9J",
        "X-Received": "by 2002:a05:6e02:1053:b0:34c:f665:861e with SMTP id\n p19-20020a056e02105300b0034cf665861emr880516ilj.21.1694120159533;\n Thu, 07 Sep 2023 13:55:59 -0700 (PDT)",
        "X-Relaying-Domain": "iol.unh.edu",
        "From": "jspewock@iol.unh.edu",
        "To": "ci@dpdk.org",
        "Cc": "aconole@redhat.com, alialnu@nvidia.com, probb@iol.unh.edu,\n ahassick@iol.unh.edu, Jeremy Spewock <jspewock@iol.unh.edu>",
        "Subject": "[PATCH v2 1/1] tools: add get_reruns script",
        "Date": "Thu,  7 Sep 2023 16:45:55 -0400",
        "Message-ID": "<20230907205551.19066-3-jspewock@iol.unh.edu>",
        "X-Mailer": "git-send-email 2.41.0",
        "In-Reply-To": "<20230907205551.19066-2-jspewock@iol.unh.edu>",
        "References": "<20230907205551.19066-2-jspewock@iol.unh.edu>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "ci@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK CI discussions <ci.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/ci>,\n <mailto:ci-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/ci/>",
        "List-Post": "<mailto:ci@dpdk.org>",
        "List-Help": "<mailto:ci-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/ci>,\n <mailto:ci-request@dpdk.org?subject=subscribe>",
        "Errors-To": "ci-bounces@dpdk.org"
    },
    "content": "From: Jeremy Spewock <jspewock@iol.unh.edu>\n\nThis script is used to interact with the DPDK Patchwork API to collect a\nlist of retests from comments on patches based on a desired list of\ncontexts to retest. The script uses regex to scan all of the comments\nsince a timestamp that is passed into the script through the CLI for\nany comment that is requesting a retest. These requests are then filtered\nbased on the desired contexts that you pass into the script through the\nCLI and then aggregated based on the patch series ID of the series that\nthe comment came from. This aggregated list is then outputted either to\na JSON file or stdout with a timestamp of the most recent comment on\npatchworks.\n\nSigned-off-by: Jeremy Spewock <jspewock@iol.unh.edu>\nSigned-off-by: Adam Hassick <ahassick@iol.unh.edu>\n---\n tools/get_reruns.py | 218 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 218 insertions(+)\n create mode 100755 tools/get_reruns.py",
    "diff": "diff --git a/tools/get_reruns.py b/tools/get_reruns.py\nnew file mode 100755\nindex 0000000..832da62\n--- /dev/null\n+++ b/tools/get_reruns.py\n@@ -0,0 +1,218 @@\n+#!/usr/bin/env python3\n+# -*- coding: utf-8 -*-\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2023 University of New Hampshire\n+\n+import argparse\n+import datetime\n+import json\n+import re\n+import requests\n+from typing import Dict, List, Optional, Set\n+\n+PATCHWORK_EVENTS_API_URL = \"http://patches.dpdk.org/api/events/\"\n+\n+\n+class JSONSetEncoder(json.JSONEncoder):\n+    \"\"\"Custom JSON encoder to handle sets.\n+\n+    Pythons json module cannot serialize sets so this custom encoder converts\n+    them into lists.\n+    \"\"\"\n+\n+    def default(self, input_object):\n+        if isinstance(input_object, set):\n+            return list(input_object)\n+        return input_object\n+\n+\n+class RerunProcessor:\n+    \"\"\"Class for finding reruns inside an email using the patchworks events\n+    API.\n+\n+    The idea of this class is to use regex to find certain patterns that\n+    represent desired contexts to rerun.\n+\n+    Arguments:\n+        desired_contexts: List of all contexts to search for in the bodies of\n+            the comments\n+        time_since: Get all comments since this timestamp\n+\n+    Attributes:\n+        collection_of_retests: A dictionary that maps patch series IDs to the\n+            set of contexts to be retested for that patch series.\n+        regex: regex used for collecting the contexts from the comment body.\n+        last_comment_timestamp: timestamp of the most recent comment that was\n+            processed\n+    \"\"\"\n+\n+    _desired_contexts: List[str]\n+    _time_since: str\n+    collection_of_retests: Dict[str, Dict[str, Set]] = {}\n+    last_comment_timestamp: Optional[str] = None\n+    # The tag we search for in comments must appear at the start of the line\n+    # and is case sensitive. After this tag we expect a comma separated list\n+    # of valid DPDK patchwork contexts.\n+    #\n+    # VALID MATCHES:\n+    #   Recheck-request: iol-unit-testing, iol-something-else, iol-one-more,\n+    #   Recheck-request: iol-unit-testing,iol-something-else, iol-one-more\n+    #   Recheck-request: iol-unit-testing, iol-example, iol-another-example,\n+    #   more-intel-testing\n+    # INVALID MATCHES:\n+    #   Recheck-request: iol-unit-testing,  intel-example-testing\n+    #   Recheck-request: iol-unit-testing iol-something-else,iol-one-more,\n+    #   Recheck-request: iol-unit-testing,iol-something-else,iol-one-more,\n+    #\n+    #   more-intel-testing\n+    regex: str = \"^Recheck-request: ((?:[a-zA-Z0-9-_]+(?:, ?\\n?)?)+)\"\n+\n+    def __init__(self, desired_contexts: List[str], time_since: str) -> None:\n+        self._desired_contexts = desired_contexts\n+        self._time_since = time_since\n+\n+    def process_reruns(self) -> None:\n+        patchwork_url = f\"{PATCHWORK_EVENTS_API_URL}?since={self._time_since}\"\n+        comment_request_info = []\n+        for item in [\n+            \"&category=cover-comment-created\",\n+            \"&category=patch-comment-created\",\n+        ]:\n+            response = requests.get(patchwork_url + item)\n+            response.raise_for_status()\n+            comment_request_info.extend(response.json())\n+        rerun_processor.process_comment_info(comment_request_info)\n+\n+    def process_comment_info(self, list_of_comment_blobs: List[Dict]) -> None:\n+        \"\"\"Takes the list of json blobs of comment information and associates\n+        them with their patches.\n+\n+        Collects retest labels from a list of comments on patches represented\n+        inlist_of_comment_blobs and creates a dictionary that associates them\n+        with their corresponding patch series ID. The labels that need to be\n+        retested are collected by passing the comments body into\n+        get_test_names() method. This method also updates the current UTC\n+        timestamp for the processor to the current time.\n+\n+        Args:\n+            list_of_comment_blobs: a list of JSON blobs that represent comment\n+            information\n+        \"\"\"\n+\n+        list_of_comment_blobs = sorted(\n+            list_of_comment_blobs,\n+            key=lambda x: datetime.datetime.fromisoformat(x[\"date\"]),\n+            reverse=True,\n+        )\n+\n+        if list_of_comment_blobs:\n+            most_recent_timestamp = datetime.datetime.fromisoformat(\n+                list_of_comment_blobs[0][\"date\"]\n+            )\n+            # exclude the most recent\n+            most_recent_timestamp = most_recent_timestamp + datetime.timedelta(\n+                microseconds=1\n+            )\n+            self.last_comment_timestamp = most_recent_timestamp.isoformat()\n+\n+        for comment in list_of_comment_blobs:\n+            # before we do any parsing we want to make sure that we are dealing\n+            # with a comment that is associated with a patch series\n+            payload_key = \"cover\"\n+            if comment[\"category\"] == \"patch-comment-created\":\n+                payload_key = \"patch\"\n+            patch_series_arr = requests.get(\n+                comment[\"payload\"][payload_key][\"url\"]\n+            ).json()[\"series\"]\n+            if not patch_series_arr:\n+                continue\n+            patch_id = patch_series_arr[0][\"id\"]\n+\n+            comment_info = requests.get(comment[\"payload\"][\"comment\"][\"url\"])\n+            comment_info.raise_for_status()\n+            content = comment_info.json()[\"content\"]\n+\n+            labels_to_rerun = self.get_test_names(content)\n+\n+            # appending to the list if it already exists, or creating it if it\n+            # doesn't\n+            if labels_to_rerun:\n+                self.collection_of_retests[patch_id] = self.collection_of_retests.get(\n+                    patch_id, {\"contexts\": set()}\n+                )\n+                self.collection_of_retests[patch_id][\"contexts\"].update(labels_to_rerun)\n+\n+    def get_test_names(self, email_body: str) -> Set[str]:\n+        \"\"\"Uses the regex in the class to get the information from the email.\n+\n+        When it gets the test names from the email, it will all be in one\n+        capture group. We expect a comma separated list of patchwork labels\n+        to be retested.\n+\n+        Returns:\n+            A set of contexts found in the email that match your list of\n+            desired contexts to capture. We use a set here to avoid duplicate\n+            contexts.\n+        \"\"\"\n+        rerun_section = re.findall(self.regex, email_body, re.MULTILINE)\n+        if not rerun_section:\n+            return set()\n+        rerun_list = list(map(str.strip, rerun_section[0].split(\",\")))\n+        return set(filter(lambda x: x and x in self._desired_contexts, rerun_list))\n+\n+    def write_output(self, file_name: str) -> None:\n+        \"\"\"Output class information.\n+\n+        Takes the collection_of_retests and last_comment_timestamp and outputs\n+        them into either a json file or stdout.\n+\n+        Args:\n+            file_name: Name of the file to write the output to. If this is set\n+            to \"-\" then it will output to stdout.\n+        \"\"\"\n+\n+        output_dict = {\n+            \"retests\": self.collection_of_retests,\n+            \"last_comment_timestamp\": self.last_comment_timestamp,\n+        }\n+        if file_name == \"-\":\n+            print(json.dumps(output_dict, indent=4, cls=JSONSetEncoder))\n+        else:\n+            with open(file_name, \"w\") as file:\n+                file.write(json.dumps(output_dict, indent=4, cls=JSONSetEncoder))\n+\n+\n+if __name__ == \"__main__\":\n+    parser = argparse.ArgumentParser(description=\"Help text for getting reruns\")\n+    parser.add_argument(\n+        \"-ts\",\n+        \"--time-since\",\n+        dest=\"time_since\",\n+        required=True,\n+        help='Get all patches since this timestamp (yyyy-mm-ddThh:mm:ss.SSSSSS).',\n+    )\n+    parser.add_argument(\n+        \"--contexts\",\n+        dest=\"contexts_to_capture\",\n+        nargs=\"*\",\n+        required=True,\n+        help='List of patchwork contexts you would like to capture.',\n+    )\n+    parser.add_argument(\n+        \"-o\",\n+        \"--out-file\",\n+        dest=\"out_file\",\n+        help=(\n+            'Output file where the list of reruns and the timestamp of the'\n+            'last comment in the list of comments is sent. If this is set'\n+            'to \"-\" then it will output to stdout (default: -).'\n+        ),\n+        default=\"-\",\n+    )\n+    args = parser.parse_args()\n+    rerun_processor = RerunProcessor(args.contexts_to_capture, args.time_since)\n+    rerun_processor.process_reruns()\n+    rerun_processor.write_output(args.out_file)\n-- \n2.41.0\n\n\n",
    "prefixes": [
        "v2",
        "1/1"
    ]
}