get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 98438,
    "url": "https://patches.dpdk.org/api/patches/98438/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20210909134808.1585777-3-mdr@ashroe.eu/",
    "project": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20210909134808.1585777-3-mdr@ashroe.eu>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20210909134808.1585777-3-mdr@ashroe.eu",
    "date": "2021-09-09T13:48:06",
    "name": "[v13,2/4] devtools: script to send notifications of expired symbols",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "881bbea5c777b65fb8d0b993f841c5fcb1605090",
    "submitter": {
        "id": 1310,
        "url": "https://patches.dpdk.org/api/people/1310/?format=api",
        "name": "Ray Kinsella",
        "email": "mdr@ashroe.eu"
    },
    "delegate": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "https://patches.dpdk.org/project/dpdk/patch/20210909134808.1585777-3-mdr@ashroe.eu/mbox/",
    "series": [
        {
            "id": 18795,
            "url": "https://patches.dpdk.org/api/series/18795/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=18795",
            "date": "2021-09-09T13:48:04",
            "name": "devtools: scripts to count and track symbols",
            "version": 13,
            "mbox": "https://patches.dpdk.org/series/18795/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/98438/comments/",
    "check": "success",
    "checks": "https://patches.dpdk.org/api/patches/98438/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-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 56E49A0547;\n\tThu,  9 Sep 2021 15:50:41 +0200 (CEST)",
            "from [217.70.189.124] (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 3E49641149;\n\tThu,  9 Sep 2021 15:50:32 +0200 (CEST)",
            "from mga12.intel.com (mga12.intel.com [192.55.52.136])\n by mails.dpdk.org (Postfix) with ESMTP id 8A4C241162\n for <dev@dpdk.org>; Thu,  9 Sep 2021 15:50:30 +0200 (CEST)",
            "from orsmga004.jf.intel.com ([10.7.209.38])\n by fmsmga106.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 09 Sep 2021 06:50:30 -0700",
            "from silpixa00396680.ir.intel.com (HELO\n silpixa00396680.ger.corp.intel.com) ([10.237.223.54])\n by orsmga004.jf.intel.com with ESMTP; 09 Sep 2021 06:50:27 -0700"
        ],
        "X-IronPort-AV": [
            "E=McAfee;i=\"6200,9189,10101\"; a=\"200318423\"",
            "E=Sophos;i=\"5.85,280,1624345200\"; d=\"scan'208\";a=\"200318423\"",
            "E=Sophos;i=\"5.85,280,1624345200\"; d=\"scan'208\";a=\"580894512\""
        ],
        "X-ExtLoop1": "1",
        "From": "Ray Kinsella <mdr@ashroe.eu>",
        "To": "dev@dpdk.org",
        "Cc": "bruce.richardson@intel.com, stephen@networkplumber.org,\n ferruh.yigit@intel.com, thomas@monjalon.net, ktraynor@redhat.com,\n mdr@ashroe.eu, aconole@redhat.com, roy.fan.zhang@intel.com,\n arkadiuszx.kusztal@intel.com, gakhil@marvell.com",
        "Date": "Thu,  9 Sep 2021 14:48:06 +0100",
        "Message-Id": "<20210909134808.1585777-3-mdr@ashroe.eu>",
        "X-Mailer": "git-send-email 2.26.2",
        "In-Reply-To": "<20210909134808.1585777-1-mdr@ashroe.eu>",
        "References": "<20210618163659.85933-1-mdr@ashroe.eu>\n <20210909134808.1585777-1-mdr@ashroe.eu>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[dpdk-dev] [PATCH v13 2/4] devtools: script to send notifications\n of expired symbols",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org",
        "Sender": "\"dev\" <dev-bounces@dpdk.org>"
    },
    "content": "Use this script with the output of the DPDK symbol tool, to notify\nmaintainers of expired symbols by email. You need to define the environment\nvariable DPDK_GETMAINTAINER_PATH for this tool to work.\n\nUse terminal output to review the emails before sending.\ne.g.\n$ devtools/symbol-tool.py list-expired --format-output csv \\\n| DPDK_GETMAINTAINER_PATH=<somewhere>/get_maintainer.pl \\\ndevtools/notify_expired_symbols.py --format-output terminal\n\nThen use email output to send the emails to the maintainers.\ne.g.\n$ devtools/symbol-tool.py list-expired --format-output csv \\\n| DPDK_GETMAINTAINER_PATH=<somewhere>/get_maintainer.pl \\\ndevtools/notify_expired_symbols.py --format-output email \\\n--smtp-server <server> --sender <someone@somewhere.com> \\\n--password <password> --cc <someone@somewhere.com>\n\nSigned-off-by: Ray Kinsella <mdr@ashroe.eu>\n---\n devtools/notify-symbol-maintainers.py | 302 ++++++++++++++++++++++++++\n 1 file changed, 302 insertions(+)\n create mode 100755 devtools/notify-symbol-maintainers.py",
    "diff": "diff --git a/devtools/notify-symbol-maintainers.py b/devtools/notify-symbol-maintainers.py\nnew file mode 100755\nindex 0000000000..edf330f88b\n--- /dev/null\n+++ b/devtools/notify-symbol-maintainers.py\n@@ -0,0 +1,302 @@\n+#!/usr/bin/env python3\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2021 Intel Corporation\n+# pylint: disable=invalid-name\n+'''Tool to notify maintainers of expired symbols'''\n+import os\n+import smtplib\n+import ssl\n+import sys\n+import subprocess\n+import argparse\n+from argparse import RawTextHelpFormatter\n+import time\n+from email.message import EmailMessage\n+from pathlib import Path\n+\n+DESCRIPTION = '''\n+Use this script with the output of the DPDK symbol tool, to notify maintainers\n+and contributors of expired symbols by email. You need to define the environment\n+variable DPDK_GETMAINTAINER_PATH for this tool to work.\n+\n+Use terminal output to review the emails before sending.\n+e.g.\n+$ devtools/symbol-tool.py list-expired --format-output csv \\\\\n+| DPDK_GETMAINTAINER_PATH=<somewhere>/get_maintainer.pl \\\\\n+{s} --format-output terminal\n+\n+Then use email output to send the emails to the maintainers.\n+e.g.\n+$ devtools/symbol-tool.py list-expired --format-output csv \\\\\n+| DPDK_GETMAINTAINER_PATH=<somewhere>/get_maintainer.pl \\\\\n+{s} --format-output email \\\\\n+--smtp-server <server> --sender <someone@somewhere.com> --password <password> \\\\\n+--cc <someone@somewhere.com>\n+'''  # noqa: E501\n+\n+EMAIL_TEMPLATE = '''Hi there,\n+\n+Please note the symbols listed below have expired. In line with the DPDK ABI\n+policy, they should be scheduled for removal, in the next DPDK release.\n+\n+For more information, please see the DPDK ABI Policy, section 3.5.3.\n+https://doc.dpdk.org/guides/contributing/abi_policy.html\n+\n+Thanks,\n+\n+The DPDK Symbol Bot\n+\n+'''  # noqa: E501\n+\n+ABI_POLICY = 'doc/guides/contributing/abi_policy.rst'\n+DPDK_GMP_ENV_VAR = 'DPDK_GETMAINTAINER_PATH'\n+MAINTAINERS = 'MAINTAINERS'\n+get_maintainer = ['devtools/get-maintainer.sh',\n+                  '--email', '-f']\n+\n+\n+class EnvironException(Exception):\n+    '''Subclass exception for Pylint\\'s happiness.'''\n+\n+\n+def _die_on_exception(e):\n+    '''Print an exception, and quit'''\n+\n+    print('Fatal Error: ' + str(e))\n+    sys.exit()\n+\n+\n+def _check_get_maintainers_env():\n+    '''Check get maintainers scripts are setup'''\n+\n+    if not Path(get_maintainer[0]).is_file():\n+        raise EnvironException('Cannot locate DPDK\\'s get maintainers script, '\n+                               ' usually at $' + get_maintainer[0] + '.')\n+\n+    if DPDK_GMP_ENV_VAR not in os.environ:\n+        raise EnvironException(DPDK_GMP_ENV_VAR + ' is not defined.')\n+\n+    if not Path(os.environ[DPDK_GMP_ENV_VAR]).is_file():\n+        raise EnvironException('Cannot locate get maintainers script, usually'\n+                               ' at ' + DPDK_GMP_ENV_VAR + '.')\n+\n+\n+def _get_maintainers(libpath):\n+    '''Get the maintainers for given library'''\n+\n+    try:\n+        _check_get_maintainers_env()\n+    except EnvironException as e:\n+        _die_on_exception(e)\n+\n+    try:\n+        cmd = get_maintainer + [libpath]\n+        result = subprocess.run(cmd,\n+                                stdout=subprocess.PIPE,\n+                                stderr=subprocess.PIPE,\n+                                check=True)\n+    except subprocess.CalledProcessError as e:\n+        _die_on_exception(e)\n+\n+    if result is None:\n+        return None\n+\n+    email = result.stdout.decode('utf-8')\n+    if email == '':\n+        return None\n+\n+    email = list(filter(None, email.split('\\n')))\n+    return email\n+\n+\n+default_maintainers = _get_maintainers(ABI_POLICY) + \\\n+    _get_maintainers(MAINTAINERS)\n+\n+\n+def get_maintainers(libpath):\n+    '''Get the maintainers for given library'''\n+    maintainers = _get_maintainers(libpath)\n+\n+    if maintainers is None:\n+        maintainers = default_maintainers\n+\n+    return maintainers\n+\n+\n+def get_message(library, symbols, config):\n+    '''Build email message from symbols, config and maintainers'''\n+    contributors = {}\n+    message = {}\n+    maintainers = get_maintainers(library)\n+\n+    if maintainers != default_maintainers:\n+        message['CC'] = default_maintainers.copy()\n+\n+    if 'CC' in config:\n+        message.setdefault('CC', []).append(config['CC'])\n+\n+    message['Subject'] = 'Expired symbols in {}\\n'.format(library)\n+\n+    body = EMAIL_TEMPLATE\n+    body += '{:<50}{:<25}{:<25}\\n'.format('Symbol', 'Contributor', 'Email')\n+    for sym in symbols:\n+        body += ('{:<50}{:<25}{:<25}\\n'.format(sym,\n+                                               symbols[sym]['name'],\n+                                               symbols[sym]['email']))\n+        email = symbols[sym]['email']\n+        contributors[email] = ''\n+\n+    contributors = list(contributors.keys())\n+\n+    message['To'] = maintainers + contributors\n+    message['Body'] = body\n+\n+    return message\n+\n+\n+class OutputEmail():\n+    '''Format the output for email'''\n+\n+    def __init__(self, config):\n+        self.config = config\n+\n+        self.terminal = OutputTerminal(config)\n+        context = ssl.create_default_context()\n+\n+        # Try to log in to server and send email\n+        try:\n+            self.server = smtplib.SMTP(config['smtp_server'], 587)\n+            self.server.starttls(context=context)  # Secure the connection\n+            self.server.login(config['sender'], config['password'])\n+        except EnvironException as e:\n+            _die_on_exception(e)\n+\n+    def message(self, message):\n+        '''send email'''\n+        self.terminal.message(message)\n+\n+        msg = EmailMessage()\n+        msg.set_content(message.pop('Body'))\n+\n+        for key in message.keys():\n+            msg[key] = message[key]\n+\n+        msg['From'] = self.config['sender']\n+        msg['Reply-To'] = 'no-reply@dpdk.org'\n+\n+        self.server.send_message(msg)\n+\n+        time.sleep(1)\n+\n+    def __del__(self):\n+        self.server.quit()\n+\n+\n+class OutputTerminal():  # pylint: disable=too-few-public-methods\n+    '''Format the output for the terminal'''\n+\n+    def __init__(self, config):\n+        self.config = config\n+\n+    def message(self, message):\n+        '''Print email to terminal'''\n+\n+        terminal = 'To:' + ', '.join(message['To']) + '\\n'\n+        if 'sender' in self.config.keys():\n+            terminal += 'From:' + self.config['sender'] + '\\n'\n+\n+        terminal += 'Reply-To:' + 'no-reply@dpdk.org' + '\\n'\n+\n+        if 'CC' in message:\n+            terminal += 'CC:' + ', '.join(message['CC']) + '\\n'\n+\n+        terminal += 'Subject:' + message['Subject'] + '\\n'\n+        terminal += 'Body:' + message['Body'] + '\\n'\n+\n+        print(terminal)\n+        print('-' * 80)\n+\n+\n+def parse_config(args):\n+    '''put the command line args in the right places'''\n+    config = {}\n+    error_msg = None\n+\n+    outputs = {\n+        None: OutputTerminal,\n+        'terminal': OutputTerminal,\n+        'email': OutputEmail\n+    }\n+\n+    if args.format_output == 'email':\n+        if args.smtp_server is None:\n+            error_msg = 'SMTP server'\n+        else:\n+            config['smtp_server'] = args.smtp_server\n+\n+        if args.sender is None:\n+            error_msg = 'sender'\n+        else:\n+            config['sender'] = args.sender\n+\n+        if args.password is None:\n+            error_msg = 'password'\n+        else:\n+            config['password'] = args.password\n+\n+    if args.cc is not None:\n+        config['CC'] = args.cc\n+\n+    if error_msg is not None:\n+        print('Please specify a {} for email output'.format(error_msg))\n+        return None\n+\n+    config['output'] = outputs[args.format_output]\n+    return config\n+\n+\n+def main():\n+    '''Main entry point'''\n+    parser = \\\n+        argparse.ArgumentParser(description=DESCRIPTION.format(s=__file__),\n+                                formatter_class=RawTextHelpFormatter)\n+    parser.add_argument('--format-output',\n+                        choices=['terminal', 'email'],\n+                        default='terminal')\n+    parser.add_argument('--smtp-server')\n+    parser.add_argument('--password')\n+    parser.add_argument('--sender')\n+    parser.add_argument('--cc')\n+\n+    args = parser.parse_args()\n+    config = parse_config(args)\n+    if config is None:\n+        return\n+\n+    symbols = {}\n+    lastlib = library = ''\n+\n+    output = config['output'](config)\n+\n+    for line in sys.stdin:\n+        line = line.rstrip('\\n')\n+\n+        if line.find('mapfile') >= 0:\n+            continue\n+        library, symbol, name, email = line.split(',')\n+\n+        if library != lastlib:\n+            message = get_message(lastlib, symbols, config)\n+            output.message(message)\n+            symbols = {}\n+\n+        lastlib = library\n+        symbols[symbol] = {'name': name, 'email': email}\n+\n+    # print the last library\n+    message = get_message(lastlib, symbols, config)\n+    output.message(message)\n+\n+\n+if __name__ == '__main__':\n+    main()\n",
    "prefixes": [
        "v13",
        "2/4"
    ]
}