get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 1558017,
    "url": "http://patchwork.ozlabs.org/api/patches/1558017/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20211122112256.2011194-2-amorenoz@redhat.com/",
    "project": {
        "id": 47,
        "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api",
        "name": "Open vSwitch",
        "link_name": "openvswitch",
        "list_id": "ovs-dev.openvswitch.org",
        "list_email": "ovs-dev@openvswitch.org",
        "web_url": "http://openvswitch.org/",
        "scm_url": "git@github.com:openvswitch/ovs.git",
        "webscm_url": "https://github.com/openvswitch/ovs",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20211122112256.2011194-2-amorenoz@redhat.com>",
    "list_archive_url": null,
    "date": "2021-11-22T11:22:39",
    "name": "[ovs-dev,v1,01/18] python: add generic Key-Value parser",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "672228e5d948732e0c08c4dacf2677ee05050237",
    "submitter": {
        "id": 77477,
        "url": "http://patchwork.ozlabs.org/api/people/77477/?format=api",
        "name": "Adrian Moreno",
        "email": "amorenoz@redhat.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20211122112256.2011194-2-amorenoz@redhat.com/mbox/",
    "series": [
        {
            "id": 273222,
            "url": "http://patchwork.ozlabs.org/api/series/273222/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=273222",
            "date": "2021-11-22T11:22:39",
            "name": "python: add flow parsing library",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/273222/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/1558017/comments/",
    "check": "fail",
    "checks": "http://patchwork.ozlabs.org/api/patches/1558017/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<ovs-dev-bounces@openvswitch.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "dev@openvswitch.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@bilbo.ozlabs.org",
            "ovs-dev@lists.linuxfoundation.org"
        ],
        "Authentication-Results": [
            "bilbo.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=a0Jy6Pvq;\n\tdkim-atps=neutral",
            "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::137; helo=smtp4.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN>)",
            "relay.mimecast.com;\n auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=amorenoz@redhat.com"
        ],
        "Received": [
            "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby bilbo.ozlabs.org (Postfix) with ESMTPS id 4HyPyy3t8Pz9sRR\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 22 Nov 2021 22:23:19 +1100 (AEDT)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id D951F403FD;\n\tMon, 22 Nov 2021 11:23:16 +0000 (UTC)",
            "from smtp4.osuosl.org ([127.0.0.1])\n\tby localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id 8TIRmOVzcVTK; Mon, 22 Nov 2021 11:23:15 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp4.osuosl.org (Postfix) with ESMTPS id 9FF7440217;\n\tMon, 22 Nov 2021 11:23:14 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id D0228C0038;\n\tMon, 22 Nov 2021 11:23:12 +0000 (UTC)",
            "from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 2CDDFC0012\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:11 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp4.osuosl.org (Postfix) with ESMTP id 1BB7B40209\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:11 +0000 (UTC)",
            "from smtp4.osuosl.org ([127.0.0.1])\n by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id jARKQqk4gQOp for <dev@openvswitch.org>;\n Mon, 22 Nov 2021 11:23:10 +0000 (UTC)",
            "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp4.osuosl.org (Postfix) with ESMTPS id DB381401D2\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:09 +0000 (UTC)",
            "from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com\n [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n us-mta-592-BhpZbcxqN06KWRj64x5mJg-1; Mon, 22 Nov 2021 06:23:05 -0500",
            "from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com\n [10.5.11.13])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 82CEE10168C0\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:04 +0000 (UTC)",
            "from amorenoz.users.ipa.redhat.com (unknown [10.2.16.196])\n by smtp.corp.redhat.com (Postfix) with ESMTP id 89AF860862;\n Mon, 22 Nov 2021 11:23:03 +0000 (UTC)"
        ],
        "X-Virus-Scanned": [
            "amavisd-new at osuosl.org",
            "amavisd-new at osuosl.org"
        ],
        "X-Greylist": "domain auto-whitelisted by SQLgrey-1.8.0",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1637580188;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=QIcXkKmSaM+haxSz0iODEjN+C6NpPy5Tq5DW8bzrVpQ=;\n b=a0Jy6Pvq9iH4OjuMx7DPMgyNq3HG6J5SuwNmDhB4tuQVGejCzBpbS8C5a0XMjGS+M2WGV0\n XWNNAUrC6eCzs0evtjUmijUM0/nCTPDClWxROqG4Cl1+CCAsrtgQMuBpB9IOq6lCcebqpX\n Mt9SW9S+E54wcy7v4O92iUn1OMiiviw=",
        "X-MC-Unique": "BhpZbcxqN06KWRj64x5mJg-1",
        "From": "Adrian Moreno <amorenoz@redhat.com>",
        "To": "dev@openvswitch.org",
        "Date": "Mon, 22 Nov 2021 12:22:39 +0100",
        "Message-Id": "<20211122112256.2011194-2-amorenoz@redhat.com>",
        "In-Reply-To": "<20211122112256.2011194-1-amorenoz@redhat.com>",
        "References": "<20211122112256.2011194-1-amorenoz@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 2.79 on 10.5.11.13",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-Originator": "redhat.com",
        "Subject": "[ovs-dev] [PATCH v1 01/18] python: add generic Key-Value parser",
        "X-BeenThere": "ovs-dev@openvswitch.org",
        "X-Mailman-Version": "2.1.15",
        "Precedence": "list",
        "List-Id": "<ovs-dev.openvswitch.org>",
        "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>",
        "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>",
        "List-Post": "<mailto:ovs-dev@openvswitch.org>",
        "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>",
        "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Errors-To": "ovs-dev-bounces@openvswitch.org",
        "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>"
    },
    "content": "Most of ofproto and dpif flows are based on key-value pairs. These\nkey-value pairs can be represented in several ways, eg: key:value,\nkey=value, key(value).\n\nAdd the following classes that allow parsing of key-value strings:\n* KeyValue: holds a key-value pair\n* KeyMetadata: holds some metadata associated with a KeyValue such as\n  the original key and value strings and their position in the global\n  string\n* KVParser: is able to parse a string and extract it's key-value pairs\n  as KeyValue instances. Before creating the KeyValue instance it tries\n  to decode the value via the KVDecoders\n* KVDecoders holds a number of decoders that KVParser can use to decode\n  key-value pairs. It accepts a dictionary of keys and callables to\n  allow users to specify what decoder (i.e: callable) to use for each\n  key\n\nAlso, flake8 seems to be incorrectly reporting an error (E203) in:\n\"slice[index + offset : index + offset]\" which is PEP8 compliant. So,\nignore this error.\n\nSigned-off-by: Adrian Moreno <amorenoz@redhat.com>\n---\n Makefile.am                  |   3 +-\n python/automake.mk           |   6 +-\n python/ovs/flows/__init__.py |   0\n python/ovs/flows/decoders.py |  19 +++\n python/ovs/flows/kv.py       | 282 +++++++++++++++++++++++++++++++++++\n python/setup.py              |   2 +-\n 6 files changed, 309 insertions(+), 3 deletions(-)\n create mode 100644 python/ovs/flows/__init__.py\n create mode 100644 python/ovs/flows/decoders.py\n create mode 100644 python/ovs/flows/kv.py",
    "diff": "diff --git a/Makefile.am b/Makefile.am\nindex cb8076433..e38dd607c 100644\n--- a/Makefile.am\n+++ b/Makefile.am\n@@ -392,6 +392,7 @@ ALL_LOCAL += flake8-check\n #   E129 visually indented line with same indent as next logical line\n #   E131 continuation line unaligned for hanging indent\n #   E722 do not use bare except, specify exception instead\n+#   E203 whitespace before ':'\n #   W503 line break before binary operator\n #   W504 line break after binary operator\n # F*** -- warnings native to flake8\n@@ -403,7 +404,7 @@ ALL_LOCAL += flake8-check\n #   H233 Python 3.x incompatible use of print operator\n #   H238 old style class declaration, use new style (inherit from `object`)\n FLAKE8_SELECT = H231,H232,H233,H238\n-FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E722,W503,W504,F811,D,H,I\n+FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E722,E203,W503,W504,F811,D,H,I\n flake8-check: $(FLAKE8_PYFILES)\n \t$(FLAKE8_WERROR)$(AM_V_GEN) \\\n \t  src='$^' && \\\ndiff --git a/python/automake.mk b/python/automake.mk\nindex 767512f17..13aa2b4c3 100644\n--- a/python/automake.mk\n+++ b/python/automake.mk\n@@ -41,7 +41,11 @@ ovs_pyfiles = \\\n \tpython/ovs/util.py \\\n \tpython/ovs/version.py \\\n \tpython/ovs/vlog.py \\\n-\tpython/ovs/winutils.py\n+\tpython/ovs/winutils.py \\\n+\tpython/ovs/flows/__init__.py \\\n+\tpython/ovs/flows/decoders.py \\\n+\tpython/ovs/flows/kv.py\n+\n # These python files are used at build time but not runtime,\n # so they are not installed.\n EXTRA_DIST += \\\ndiff --git a/python/ovs/flows/__init__.py b/python/ovs/flows/__init__.py\nnew file mode 100644\nindex 000000000..e69de29bb\ndiff --git a/python/ovs/flows/decoders.py b/python/ovs/flows/decoders.py\nnew file mode 100644\nindex 000000000..bfb64e70e\n--- /dev/null\n+++ b/python/ovs/flows/decoders.py\n@@ -0,0 +1,19 @@\n+\"\"\" Defines helpful decoders that can be used to decode information from the\n+flows\n+\n+A decoder is generally a callable that accepts a string and returns the value\n+object.\n+\"\"\"\n+\n+\n+def decode_default(value):\n+    \"\"\"Default decoder.\n+\n+    It tries to convert into an integer value and, if it fails, just\n+    returns the string.\n+    \"\"\"\n+    try:\n+        ival = int(value, 0)\n+        return ival\n+    except ValueError:\n+        return value\ndiff --git a/python/ovs/flows/kv.py b/python/ovs/flows/kv.py\nnew file mode 100644\nindex 000000000..0093a4e90\n--- /dev/null\n+++ b/python/ovs/flows/kv.py\n@@ -0,0 +1,282 @@\n+\"\"\" Common helper classes for flow (ofproto/dpif) parsing\n+\"\"\"\n+\n+import re\n+import functools\n+\n+from ovs.flows.decoders import decode_default\n+\n+\n+class ParseError(RuntimeError):\n+    \"\"\"Exception raised when an error occurs during parsing.\"\"\"\n+\n+    pass\n+\n+\n+class KeyMetadata:\n+    \"\"\"Class for keeping key metadata.\n+\n+    Attributes:\n+        kpos (int): The position of the keyword in the parent string.\n+        vpos (int): The position of the value in the parent string.\n+        kstring (string): The keyword string as found in the flow string.\n+        vstring (string): The value as found in the flow string.\n+        end_del (bool): Whether the key has end delimiter.\n+    \"\"\"\n+\n+    def __init__(self, kpos, vpos, kstring, vstring, delim=\"\", end_delim=\"\"):\n+        \"\"\"Constructor\"\"\"\n+        self.kpos = kpos\n+        self.vpos = vpos\n+        self.kstring = kstring\n+        self.vstring = vstring\n+        self.delim = delim\n+        self.end_delim = end_delim\n+\n+    def __str__(self):\n+        return \"key: [{},{}), val:[{}, {})\".format(\n+            self.kpos,\n+            self.kpos + len(self.kstring),\n+            self.vpos,\n+            self.vpos + len(self.vstring),\n+        )\n+\n+    def __repr__(self):\n+        return \"%s('%s')\" % (self.__class__.__name__, self)\n+\n+\n+class KeyValue:\n+    \"\"\"Class for keeping key-value data\n+\n+    Attributes:\n+        key (str): The key string.\n+        value (any): The value data.\n+        meta (KeyMetadata): The key metadata.\n+    \"\"\"\n+\n+    def __init__(self, key, value, meta=None):\n+        \"\"\"Constructor\"\"\"\n+        self.key = key\n+        self.value = value\n+        self.meta = meta\n+\n+    def __str__(self):\n+        return \"{}: {} ({})\".format(self.key, str(self.value), str(self.meta))\n+\n+    def __repr__(self):\n+        return \"%s('%s')\" % (self.__class__.__name__, self)\n+\n+\n+class KVDecoders:\n+    \"\"\"KVDecoders class is used by KVParser to select how to decoode the value\n+    of a specific keyword.\n+\n+    A decoder is simply a function that accepts a value string\n+    and returns the value objects to be stored.\n+    The returned value may be of any type.\n+\n+    Decoders may return a KeyValue instance to indicate that the keyword should\n+    also be modified to match the one provided in the returned KeyValue\n+\n+    The free_decoder, however, must return the key and value to be stored\n+\n+    Args:\n+        decoders (dict): Optional; A dictionary of decoders indexed by keyword.\n+        default (callable): Optional; A decoder used if a match is not found in\n+            configured decoders. If not provided, the default behavior is to\n+            try to decode the value into an integer and, if that fails,\n+            just return the string as-is.\n+        default_free (callable): Optional; The decoder used if a match is not\n+            found in configured decoders and it's a free value (e.g:\n+            a value without a key) Defaults to returning the free value as\n+            keyword and \"True\" as value.\n+            The callable must accept a string and return a key, value pair\n+    \"\"\"\n+\n+    def __init__(self, decoders=None, default=None, default_free=None):\n+        self._decoders = decoders or dict()\n+        self._default = default or decode_default\n+        self._default_free = default_free or self._default_free_decoder\n+\n+    def decode(self, keyword, value_str):\n+        \"\"\"Decode a keyword and value.\n+\n+        Args:\n+            keyword (str): The keyword whose value is to be decoded.\n+            value_str (str): The value string.\n+\n+        Returns:\n+            The key (str) and value(any) to be stored.\n+        \"\"\"\n+\n+        decoder = self._decoders.get(keyword)\n+        if decoder:\n+            result = decoder(value_str)\n+            if isinstance(result, KeyValue):\n+                keyword = result.key\n+                value = result.value\n+            else:\n+                value = result\n+\n+            return keyword, value\n+        else:\n+            if value_str:\n+                return keyword, self._default(value_str)\n+            else:\n+                return self._default_free(keyword)\n+\n+    @staticmethod\n+    def _default_free_decoder(key):\n+        \"\"\"Default decoder for free kewords.\"\"\"\n+        return key, True\n+\n+\n+delim_pattern = re.compile(r\"(\\(|=|:|,|\\n|\\r|\\t|$)\")\n+parenthesys_pattern = re.compile(r\"(\\(|\\))\")\n+end_pattern = re.compile(r\"( |,|\\n|\\r|\\t)\")\n+\n+\n+class KVParser:\n+    \"\"\"KVParser parses a string looking for key-value pairs.\n+\n+    Args:\n+        decoders (KVDecoders): Optional; the KVDecoders instance to use.\n+    \"\"\"\n+\n+    def __init__(self, decoders=None):\n+        \"\"\"Constructor\"\"\"\n+        self._decoders = decoders or KVDecoders()\n+        self._keyval = list()\n+\n+    def keys(self):\n+        return list(kv.key for kv in self._keyval)\n+\n+    def kv(self):\n+        return self._keyval\n+\n+    def __iter__(self):\n+        return iter(self._keyval)\n+\n+    def parse(self, string):\n+        \"\"\"Parse the key-value pairs in string.\n+\n+        Args:\n+            string (str): the string to parse.\n+\n+        Raises:\n+            ParseError if any parsing error occurs.\n+        \"\"\"\n+        kpos = 0\n+        while kpos < len(string) and string[kpos] != \"\\n\":\n+            # strip string\n+            if string[kpos] == \",\" or string[kpos] == \" \":\n+                kpos += 1\n+                continue\n+\n+            split_parts = delim_pattern.split(string[kpos:], 1)\n+            # the delimiter should be included in the returned list\n+            if len(split_parts) < 3:\n+                break\n+\n+            keyword = split_parts[0]\n+            delimiter = split_parts[1]\n+            rest = split_parts[2]\n+\n+            value_str = \"\"\n+            vpos = kpos + len(keyword) + 1\n+            end_delimiter = \"\"\n+\n+            # Figure out the end of the value\n+            # If the delimiter is ':' or '=', the end of the value is the end\n+            # of the string or a ', '\n+            if delimiter in (\"=\", \":\"):\n+                value_parts = end_pattern.split(rest, 1)\n+                value_str = value_parts[0] if len(value_parts) == 3 else rest\n+                next_kpos = vpos + len(value_str)\n+\n+            elif delimiter == \"(\":\n+                # Find the next ')'\n+                level = 1\n+                index = 0\n+                value_parts = parenthesys_pattern.split(rest)\n+                for val in value_parts:\n+                    if val == \"(\":\n+                        level += 1\n+                    elif val == \")\":\n+                        level -= 1\n+                    index += len(val)\n+                    if level == 0:\n+                        break\n+\n+                if level != 0:\n+                    raise ParseError(\n+                        \"Error parsing string {}: \"\n+                        \"Failed to find matching ')' in {}\".format(\n+                            string, rest\n+                        )\n+                    )\n+\n+                value_str = rest[: index - 1]\n+                next_kpos = vpos + len(value_str) + 1\n+                end_delimiter = \")\"\n+\n+                # Exceptionally, if after the () we find -> {}, do not treat\n+                # the content of the parenthesis as the value, consider\n+                # ({})->{} as the string value\n+                if index < len(rest) - 2 and rest[index : index + 2] == \"->\":\n+                    extra_val = rest[index + 2 :].split(\",\")[0]\n+                    value_str = \"({})->{}\".format(value_str, extra_val)\n+                    # remove the first \"(\"\n+                    vpos -= 1\n+                    next_kpos = vpos + len(value_str)\n+                    end_delimiter = \"\"\n+\n+            elif delimiter in (\",\", \"\\n\", \"\\t\", \"\\r\", \"\"):\n+                # key with no value\n+                next_kpos = kpos + len(keyword)\n+                vpos = -1\n+\n+            try:\n+                key, val = self._decoders.decode(keyword, value_str)\n+            except Exception as e:\n+                raise ParseError(\n+                    \"Error parsing key-value ({}, {})\".format(\n+                        keyword, value_str\n+                    )\n+                ) from e\n+\n+            meta = KeyMetadata(\n+                kpos=kpos,\n+                vpos=vpos,\n+                kstring=keyword,\n+                vstring=value_str,\n+                delim=delimiter,\n+                end_delim=end_delimiter,\n+            )\n+\n+            self._keyval.append(KeyValue(key, val, meta))\n+\n+            kpos = next_kpos\n+\n+\n+def decode_nested_kv(decoders, value):\n+    \"\"\"A key-value decoder that extracts nested key-value pairs and returns\n+    them in a dictionary\n+\n+    Args:\n+        decoders (KVDecoders): the KVDecoders to use.\n+        value (str): the value string to decode.\n+    \"\"\"\n+    if not value:\n+        # Mark as flag\n+        return True\n+\n+    parser = KVParser(decoders)\n+    parser.parse(value)\n+    return {kv.key: kv.value for kv in parser.kv()}\n+\n+\n+def nested_kv_decoder(decoders=None):\n+    \"\"\"Helper function that creates a nested kv decoder with given\n+    KVDecoders\"\"\"\n+    return functools.partial(decode_nested_kv, decoders)\ndiff --git a/python/setup.py b/python/setup.py\nindex cfe01763f..0e6b0ea39 100644\n--- a/python/setup.py\n+++ b/python/setup.py\n@@ -71,7 +71,7 @@ setup_args = dict(\n     author='Open vSwitch',\n     author_email='dev@openvswitch.org',\n     packages=['ovs', 'ovs.compat', 'ovs.compat.sortedcontainers',\n-              'ovs.db', 'ovs.unixctl'],\n+              'ovs.db', 'ovs.unixctl', 'ovs.flows'],\n     keywords=['openvswitch', 'ovs', 'OVSDB'],\n     license='Apache 2.0',\n     classifiers=[\n",
    "prefixes": [
        "ovs-dev",
        "v1",
        "01/18"
    ]
}