get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

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

{
    "id": 133911,
    "url": "http://patches.dpdk.org/api/patches/133911/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-11-juraj.linkes@pantheon.tech/",
    "project": {
        "id": 1,
        "url": "http://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": "<20231106171601.160749-11-juraj.linkes@pantheon.tech>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20231106171601.160749-11-juraj.linkes@pantheon.tech",
    "date": "2023-11-06T17:15:48",
    "name": "[v5,10/23] dts: config docstring update",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "11baa06779fd08ea6b719403be95e12dc08a726b",
    "submitter": {
        "id": 1626,
        "url": "http://patches.dpdk.org/api/people/1626/?format=api",
        "name": "Juraj Linkeš",
        "email": "juraj.linkes@pantheon.tech"
    },
    "delegate": {
        "id": 2642,
        "url": "http://patches.dpdk.org/api/users/2642/?format=api",
        "username": "mcoquelin",
        "first_name": "Maxime",
        "last_name": "Coquelin",
        "email": "maxime.coquelin@redhat.com"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20231106171601.160749-11-juraj.linkes@pantheon.tech/mbox/",
    "series": [
        {
            "id": 30173,
            "url": "http://patches.dpdk.org/api/series/30173/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=30173",
            "date": "2023-11-06T17:15:38",
            "name": "dts: add dts api docs",
            "version": 5,
            "mbox": "http://patches.dpdk.org/series/30173/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/133911/comments/",
    "check": "success",
    "checks": "http://patches.dpdk.org/api/patches/133911/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 B029B432BB;\n\tMon,  6 Nov 2023 18:17:32 +0100 (CET)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id F037041144;\n\tMon,  6 Nov 2023 18:16:21 +0100 (CET)",
            "from mail-ej1-f54.google.com (mail-ej1-f54.google.com\n [209.85.218.54]) by mails.dpdk.org (Postfix) with ESMTP id A392E410F9\n for <dev@dpdk.org>; Mon,  6 Nov 2023 18:16:19 +0100 (CET)",
            "by mail-ej1-f54.google.com with SMTP id\n a640c23a62f3a-9c5b313b3ffso706253366b.0\n for <dev@dpdk.org>; Mon, 06 Nov 2023 09:16:19 -0800 (PST)",
            "from jlinkes-PT-Latitude-5530.. (ip-46.34.243.197.o2inet.sk.\n [46.34.243.197]) by smtp.gmail.com with ESMTPSA id\n s10-20020a170906354a00b009b947aacb4bsm47016eja.191.2023.11.06.09.16.17\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 06 Nov 2023 09:16:18 -0800 (PST)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=pantheon.tech; s=google; t=1699290979; x=1699895779; 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=UPLVAuBLaKUHh0e7ghZ5KBaj+aS4lDT4JMKEmjiTvQs=;\n b=aJXXm7zC67JMmFM4j76acRsw2iv7c90h4gzTTmgjkNztZzuNIo3V5isty72S8ZLNje\n xJzjPIS4AaXKIxr17KF1x0Fn4HRyRThxKjvOTPcAe/hm8re+7tjP3nOXQnwjn5xZe6Od\n c8C8/740oAD6wdTVUYhOrMMHxvfTWVP8ten1maVpyDTq+ClBaGJdUY3DaMKGecLXJfjj\n j53H+gNhxI7AqwV5rIsMCHS7zQlayJ2EYAFL4czNKhte0rc8lfpZIAucW0U5L4s2b1LU\n qvwjOiCwnymlPd2nrukXmsy+/sRuUK80cHvGmlIcj8E9lkta7jm0W0M2kDElaam/KDEe\n dsSQ==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1699290979; x=1699895779;\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=UPLVAuBLaKUHh0e7ghZ5KBaj+aS4lDT4JMKEmjiTvQs=;\n b=ilti/hTpKis/s1RAZEIyCDnGEKF79v6ZTkMk85z/wMQipwbsMwkWjO+jM2nYTLtVYi\n 7FS5zz+BFUGrm+FTQNiEhG4pvI0AwM6oZrxch3GSG+4M5fwu98xV/fPzYPZ4JN954n8w\n G+Oj7nS7mUB0Q+5VJ7NnAhu0PBvP9OpVJimNW8DVf0zjjyLNP9WsqqdBIm0QUU/piIYU\n 0Utr2j6lKxi5oVaJeDkIcqae2H0hDDX0yFYJzlj9XiUixCIAAMY95+9LnQKLJ5mxYPba\n t3gD54qyPawXVonVvpqsuqqZMeoDlX/OwqtPu1pM71hBOnClg7NrzJYYy9cEP34n5wCz\n p5rA==",
        "X-Gm-Message-State": "AOJu0YysD807b6fPVWDkSDMjbpLsrvqQnrEk9by4YsbSVPErRycsqJuz\n maZhzz74H8Ysx+Y76f9GKMr9I4ynF5OzuyP41uSgBA==",
        "X-Google-Smtp-Source": "\n AGHT+IH7B2qxW+KAsr/1QdVGV3YzAufHfiCA0AIva+iue9V1QkPB/qzVtyQID2tFatOFBmz39Zq8ag==",
        "X-Received": "by 2002:a17:906:6a1a:b0:9a1:891b:6eed with SMTP id\n qw26-20020a1709066a1a00b009a1891b6eedmr15229810ejc.76.1699290979134;\n Mon, 06 Nov 2023 09:16:19 -0800 (PST)",
        "From": "=?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "To": "thomas@monjalon.net, Honnappa.Nagarahalli@arm.com,\n bruce.richardson@intel.com, jspewock@iol.unh.edu, probb@iol.unh.edu,\n paul.szczepanek@arm.com, yoan.picchi@foss.arm.com",
        "Cc": "dev@dpdk.org, =?utf-8?q?Juraj_Linke=C5=A1?= <juraj.linkes@pantheon.tech>",
        "Subject": "[PATCH v5 10/23] dts: config docstring update",
        "Date": "Mon,  6 Nov 2023 18:15:48 +0100",
        "Message-Id": "<20231106171601.160749-11-juraj.linkes@pantheon.tech>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20231106171601.160749-1-juraj.linkes@pantheon.tech>",
        "References": "<20230831100407.59865-1-juraj.linkes@pantheon.tech>\n <20231106171601.160749-1-juraj.linkes@pantheon.tech>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "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"
    },
    "content": "Format according to the Google format and PEP257, with slight\ndeviations.\n\nSigned-off-by: Juraj Linkeš <juraj.linkes@pantheon.tech>\n---\n dts/framework/config/__init__.py | 371 ++++++++++++++++++++++++++-----\n dts/framework/config/types.py    | 132 +++++++++++\n 2 files changed, 446 insertions(+), 57 deletions(-)\n create mode 100644 dts/framework/config/types.py",
    "diff": "diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py\nindex 2044c82611..0aa149a53d 100644\n--- a/dts/framework/config/__init__.py\n+++ b/dts/framework/config/__init__.py\n@@ -3,8 +3,34 @@\n # Copyright(c) 2022-2023 University of New Hampshire\n # Copyright(c) 2023 PANTHEON.tech s.r.o.\n \n-\"\"\"\n-Yaml config parsing methods\n+\"\"\"Testbed configuration and test suite specification.\n+\n+This package offers classes that hold real-time information about the testbed, hold test run\n+configuration describing the tested testbed and a loader function, :func:`load_config`, which loads\n+the YAML test run configuration file\n+and validates it according to :download:`the schema <conf_yaml_schema.json>`.\n+\n+The YAML test run configuration file is parsed into a dictionary, parts of which are used throughout\n+this package. The allowed keys and types inside this dictionary are defined in\n+the :doc:`types <framework.config.types>` module.\n+\n+The test run configuration has two main sections:\n+\n+    * The :class:`ExecutionConfiguration` which defines what tests are going to be run\n+      and how DPDK will be built. It also references the testbed where these tests and DPDK\n+      are going to be run,\n+    * The nodes of the testbed are defined in the other section,\n+      a :class:`list` of :class:`NodeConfiguration` objects.\n+\n+The real-time information about testbed is supposed to be gathered at runtime.\n+\n+The classes defined in this package make heavy use of :mod:`dataclasses`.\n+All of them use slots and are frozen:\n+\n+    * Slots enables some optimizations, by pre-allocating space for the defined\n+      attributes in the underlying data structure,\n+    * Frozen makes the object immutable. This enables further optimizations,\n+      and makes it thread safe should we every want to move in that direction.\n \"\"\"\n \n import json\n@@ -12,11 +38,20 @@\n import pathlib\n from dataclasses import dataclass\n from enum import auto, unique\n-from typing import Any, TypedDict, Union\n+from typing import Union\n \n import warlock  # type: ignore[import]\n import yaml\n \n+from framework.config.types import (\n+    BuildTargetConfigDict,\n+    ConfigurationDict,\n+    ExecutionConfigDict,\n+    NodeConfigDict,\n+    PortConfigDict,\n+    TestSuiteConfigDict,\n+    TrafficGeneratorConfigDict,\n+)\n from framework.exception import ConfigurationError\n from framework.settings import SETTINGS\n from framework.utils import StrEnum\n@@ -24,55 +59,97 @@\n \n @unique\n class Architecture(StrEnum):\n+    r\"\"\"The supported architectures of :class:`~framework.testbed_model.node.Node`\\s.\"\"\"\n+\n+    #:\n     i686 = auto()\n+    #:\n     x86_64 = auto()\n+    #:\n     x86_32 = auto()\n+    #:\n     arm64 = auto()\n+    #:\n     ppc64le = auto()\n \n \n @unique\n class OS(StrEnum):\n+    r\"\"\"The supported operating systems of :class:`~framework.testbed_model.node.Node`\\s.\"\"\"\n+\n+    #:\n     linux = auto()\n+    #:\n     freebsd = auto()\n+    #:\n     windows = auto()\n \n \n @unique\n class CPUType(StrEnum):\n+    r\"\"\"The supported CPUs of :class:`~framework.testbed_model.node.Node`\\s.\"\"\"\n+\n+    #:\n     native = auto()\n+    #:\n     armv8a = auto()\n+    #:\n     dpaa2 = auto()\n+    #:\n     thunderx = auto()\n+    #:\n     xgene1 = auto()\n \n \n @unique\n class Compiler(StrEnum):\n+    r\"\"\"The supported compilers of :class:`~framework.testbed_model.node.Node`\\s.\"\"\"\n+\n+    #:\n     gcc = auto()\n+    #:\n     clang = auto()\n+    #:\n     icc = auto()\n+    #:\n     msvc = auto()\n \n \n @unique\n class TrafficGeneratorType(StrEnum):\n+    \"\"\"The supported traffic generators.\"\"\"\n+\n+    #:\n     SCAPY = auto()\n \n \n-# Slots enables some optimizations, by pre-allocating space for the defined\n-# attributes in the underlying data structure.\n-#\n-# Frozen makes the object immutable. This enables further optimizations,\n-# and makes it thread safe should we every want to move in that direction.\n @dataclass(slots=True, frozen=True)\n class HugepageConfiguration:\n+    r\"\"\"The hugepage configuration of :class:`~framework.testbed_model.node.Node`\\s.\n+\n+    Attributes:\n+        amount: The number of hugepages.\n+        force_first_numa: If :data:`True`, the hugepages will be configured on the first NUMA node.\n+    \"\"\"\n+\n     amount: int\n     force_first_numa: bool\n \n \n @dataclass(slots=True, frozen=True)\n class PortConfig:\n+    r\"\"\"The port configuration of :class:`~framework.testbed_model.node.Node`\\s.\n+\n+    Attributes:\n+        node: The :class:`~framework.testbed_model.node.Node` where this port exists.\n+        pci: The PCI address of the port.\n+        os_driver_for_dpdk: The operating system driver name for use with DPDK.\n+        os_driver: The operating system driver name when the operating system controls the port.\n+        peer_node: The :class:`~framework.testbed_model.node.Node` of the port\n+            connected to this port.\n+        peer_pci: The PCI address of the port connected to this port.\n+    \"\"\"\n+\n     node: str\n     pci: str\n     os_driver_for_dpdk: str\n@@ -81,18 +158,44 @@ class PortConfig:\n     peer_pci: str\n \n     @staticmethod\n-    def from_dict(node: str, d: dict) -> \"PortConfig\":\n+    def from_dict(node: str, d: PortConfigDict) -> \"PortConfig\":\n+        \"\"\"A convenience method that creates the object from fewer inputs.\n+\n+        Args:\n+            node: The node where this port exists.\n+            d: The configuration dictionary.\n+\n+        Returns:\n+            The port configuration instance.\n+        \"\"\"\n         return PortConfig(node=node, **d)\n \n \n @dataclass(slots=True, frozen=True)\n class TrafficGeneratorConfig:\n+    \"\"\"The configuration of traffic generators.\n+\n+    The class will be expanded when more configuration is needed.\n+\n+    Attributes:\n+        traffic_generator_type: The type of the traffic generator.\n+    \"\"\"\n+\n     traffic_generator_type: TrafficGeneratorType\n \n     @staticmethod\n-    def from_dict(d: dict) -> \"ScapyTrafficGeneratorConfig\":\n-        # This looks useless now, but is designed to allow expansion to traffic\n-        # generators that require more configuration later.\n+    def from_dict(d: TrafficGeneratorConfigDict) -> \"ScapyTrafficGeneratorConfig\":\n+        \"\"\"A convenience method that produces traffic generator config of the proper type.\n+\n+        Args:\n+            d: The configuration dictionary.\n+\n+        Returns:\n+            The traffic generator configuration instance.\n+\n+        Raises:\n+            ConfigurationError: An unknown traffic generator type was encountered.\n+        \"\"\"\n         match TrafficGeneratorType(d[\"type\"]):\n             case TrafficGeneratorType.SCAPY:\n                 return ScapyTrafficGeneratorConfig(\n@@ -106,11 +209,31 @@ def from_dict(d: dict) -> \"ScapyTrafficGeneratorConfig\":\n \n @dataclass(slots=True, frozen=True)\n class ScapyTrafficGeneratorConfig(TrafficGeneratorConfig):\n+    \"\"\"Scapy traffic generator specific configuration.\"\"\"\n+\n     pass\n \n \n @dataclass(slots=True, frozen=True)\n class NodeConfiguration:\n+    r\"\"\"The configuration of :class:`~framework.testbed_model.node.Node`\\s.\n+\n+    Attributes:\n+        name: The name of the :class:`~framework.testbed_model.node.Node`.\n+        hostname: The hostname of the :class:`~framework.testbed_model.node.Node`.\n+            Can be an IP or a domain name.\n+        user: The name of the user used to connect to\n+            the :class:`~framework.testbed_model.node.Node`.\n+        password: The password of the user. The use of passwords is heavily discouraged.\n+            Please use keys instead.\n+        arch: The architecture of the :class:`~framework.testbed_model.node.Node`.\n+        os: The operating system of the :class:`~framework.testbed_model.node.Node`.\n+        lcores: A comma delimited list of logical cores to use when running DPDK.\n+        use_first_core: If :data:`True`, the first logical core won't be used.\n+        hugepages: An optional hugepage configuration.\n+        ports: The ports that can be used in testing.\n+    \"\"\"\n+\n     name: str\n     hostname: str\n     user: str\n@@ -123,57 +246,91 @@ class NodeConfiguration:\n     ports: list[PortConfig]\n \n     @staticmethod\n-    def from_dict(d: dict) -> Union[\"SutNodeConfiguration\", \"TGNodeConfiguration\"]:\n-        hugepage_config = d.get(\"hugepages\")\n-        if hugepage_config:\n-            if \"force_first_numa\" not in hugepage_config:\n-                hugepage_config[\"force_first_numa\"] = False\n-            hugepage_config = HugepageConfiguration(**hugepage_config)\n-\n-        common_config = {\n-            \"name\": d[\"name\"],\n-            \"hostname\": d[\"hostname\"],\n-            \"user\": d[\"user\"],\n-            \"password\": d.get(\"password\"),\n-            \"arch\": Architecture(d[\"arch\"]),\n-            \"os\": OS(d[\"os\"]),\n-            \"lcores\": d.get(\"lcores\", \"1\"),\n-            \"use_first_core\": d.get(\"use_first_core\", False),\n-            \"hugepages\": hugepage_config,\n-            \"ports\": [PortConfig.from_dict(d[\"name\"], port) for port in d[\"ports\"]],\n-        }\n-\n+    def from_dict(\n+        d: NodeConfigDict,\n+    ) -> Union[\"SutNodeConfiguration\", \"TGNodeConfiguration\"]:\n+        \"\"\"A convenience method that processes the inputs before creating a specialized instance.\n+\n+        Args:\n+            d: The configuration dictionary.\n+\n+        Returns:\n+            Either an SUT or TG configuration instance.\n+        \"\"\"\n+        hugepage_config = None\n+        if \"hugepages\" in d:\n+            hugepage_config_dict = d[\"hugepages\"]\n+            if \"force_first_numa\" not in hugepage_config_dict:\n+                hugepage_config_dict[\"force_first_numa\"] = False\n+            hugepage_config = HugepageConfiguration(**hugepage_config_dict)\n+\n+        # The calls here contain duplicated code which is here because Mypy doesn't\n+        # properly support dictionary unpacking with TypedDicts\n         if \"traffic_generator\" in d:\n             return TGNodeConfiguration(\n+                name=d[\"name\"],\n+                hostname=d[\"hostname\"],\n+                user=d[\"user\"],\n+                password=d.get(\"password\"),\n+                arch=Architecture(d[\"arch\"]),\n+                os=OS(d[\"os\"]),\n+                lcores=d.get(\"lcores\", \"1\"),\n+                use_first_core=d.get(\"use_first_core\", False),\n+                hugepages=hugepage_config,\n+                ports=[PortConfig.from_dict(d[\"name\"], port) for port in d[\"ports\"]],\n                 traffic_generator=TrafficGeneratorConfig.from_dict(\n                     d[\"traffic_generator\"]\n                 ),\n-                **common_config,\n             )\n         else:\n             return SutNodeConfiguration(\n-                memory_channels=d.get(\"memory_channels\", 1), **common_config\n+                name=d[\"name\"],\n+                hostname=d[\"hostname\"],\n+                user=d[\"user\"],\n+                password=d.get(\"password\"),\n+                arch=Architecture(d[\"arch\"]),\n+                os=OS(d[\"os\"]),\n+                lcores=d.get(\"lcores\", \"1\"),\n+                use_first_core=d.get(\"use_first_core\", False),\n+                hugepages=hugepage_config,\n+                ports=[PortConfig.from_dict(d[\"name\"], port) for port in d[\"ports\"]],\n+                memory_channels=d.get(\"memory_channels\", 1),\n             )\n \n \n @dataclass(slots=True, frozen=True)\n class SutNodeConfiguration(NodeConfiguration):\n+    \"\"\":class:`~framework.testbed_model.sut_node.SutNode` specific configuration.\n+\n+    Attributes:\n+        memory_channels: The number of memory channels to use when running DPDK.\n+    \"\"\"\n+\n     memory_channels: int\n \n \n @dataclass(slots=True, frozen=True)\n class TGNodeConfiguration(NodeConfiguration):\n+    \"\"\":class:`~framework.testbed_model.tg_node.TGNode` specific configuration.\n+\n+    Attributes:\n+        traffic_generator: The configuration of the traffic generator present on the TG node.\n+    \"\"\"\n+\n     traffic_generator: ScapyTrafficGeneratorConfig\n \n \n @dataclass(slots=True, frozen=True)\n class NodeInfo:\n-    \"\"\"Class to hold important versions within the node.\n-\n-    This class, unlike the NodeConfiguration class, cannot be generated at the start.\n-    This is because we need to initialize a connection with the node before we can\n-    collect the information needed in this class. Therefore, it cannot be a part of\n-    the configuration class above.\n+    \"\"\"Supplemental node information.\n+\n+    Attributes:\n+        os_name: The name of the running operating system of\n+            the :class:`~framework.testbed_model.node.Node`.\n+        os_version: The version of the running operating system of\n+            the :class:`~framework.testbed_model.node.Node`.\n+        kernel_version: The kernel version of the running operating system of\n+            the :class:`~framework.testbed_model.node.Node`.\n     \"\"\"\n \n     os_name: str\n@@ -183,6 +340,20 @@ class NodeInfo:\n \n @dataclass(slots=True, frozen=True)\n class BuildTargetConfiguration:\n+    \"\"\"DPDK build configuration.\n+\n+    The configuration used for building DPDK.\n+\n+    Attributes:\n+        arch: The target architecture to build for.\n+        os: The target os to build for.\n+        cpu: The target CPU to build for.\n+        compiler: The compiler executable to use.\n+        compiler_wrapper: This string will be put in front of the compiler when\n+            executing the build. Useful for adding wrapper commands, such as ``ccache``.\n+        name: The name of the compiler.\n+    \"\"\"\n+\n     arch: Architecture\n     os: OS\n     cpu: CPUType\n@@ -191,7 +362,18 @@ class BuildTargetConfiguration:\n     name: str\n \n     @staticmethod\n-    def from_dict(d: dict) -> \"BuildTargetConfiguration\":\n+    def from_dict(d: BuildTargetConfigDict) -> \"BuildTargetConfiguration\":\n+        r\"\"\"A convenience method that processes the inputs before creating an instance.\n+\n+        `arch`, `os`, `cpu` and `compiler` are converted to :class:`Enum`\\s and\n+        `name` is constructed from `arch`, `os`, `cpu` and `compiler`.\n+\n+        Args:\n+            d: The configuration dictionary.\n+\n+        Returns:\n+            The build target configuration instance.\n+        \"\"\"\n         return BuildTargetConfiguration(\n             arch=Architecture(d[\"arch\"]),\n             os=OS(d[\"os\"]),\n@@ -204,23 +386,29 @@ def from_dict(d: dict) -> \"BuildTargetConfiguration\":\n \n @dataclass(slots=True, frozen=True)\n class BuildTargetInfo:\n-    \"\"\"Class to hold important versions within the build target.\n+    \"\"\"Various versions and other information about a build target.\n \n-    This is very similar to the NodeInfo class, it just instead holds information\n-    for the build target.\n+    Attributes:\n+        dpdk_version: The DPDK version that was built.\n+        compiler_version: The version of the compiler used to build DPDK.\n     \"\"\"\n \n     dpdk_version: str\n     compiler_version: str\n \n \n-class TestSuiteConfigDict(TypedDict):\n-    suite: str\n-    cases: list[str]\n-\n-\n @dataclass(slots=True, frozen=True)\n class TestSuiteConfig:\n+    \"\"\"Test suite configuration.\n+\n+    Information about a single test suite to be executed.\n+\n+    Attributes:\n+        test_suite: The name of the test suite module without the starting ``TestSuite_``.\n+        test_cases: The names of test cases from this test suite to execute.\n+            If empty, all test cases will be executed.\n+    \"\"\"\n+\n     test_suite: str\n     test_cases: list[str]\n \n@@ -228,6 +416,14 @@ class TestSuiteConfig:\n     def from_dict(\n         entry: str | TestSuiteConfigDict,\n     ) -> \"TestSuiteConfig\":\n+        \"\"\"Create an instance from two different types.\n+\n+        Args:\n+            entry: Either a suite name or a dictionary containing the config.\n+\n+        Returns:\n+            The test suite configuration instance.\n+        \"\"\"\n         if isinstance(entry, str):\n             return TestSuiteConfig(test_suite=entry, test_cases=[])\n         elif isinstance(entry, dict):\n@@ -238,19 +434,49 @@ def from_dict(\n \n @dataclass(slots=True, frozen=True)\n class ExecutionConfiguration:\n+    \"\"\"The configuration of an execution.\n+\n+    The configuration contains testbed information, what tests to execute\n+    and with what DPDK build.\n+\n+    Attributes:\n+        build_targets: A list of DPDK builds to test.\n+        perf: Whether to run performance tests.\n+        func: Whether to run functional tests.\n+        skip_smoke_tests: Whether to skip smoke tests.\n+        test_suites: The names of test suites and/or test cases to execute.\n+        system_under_test_node: The SUT node to use in this execution.\n+        traffic_generator_node: The TG node to use in this execution.\n+        vdevs: The names of virtual devices to test.\n+    \"\"\"\n+\n     build_targets: list[BuildTargetConfiguration]\n     perf: bool\n     func: bool\n+    skip_smoke_tests: bool\n     test_suites: list[TestSuiteConfig]\n     system_under_test_node: SutNodeConfiguration\n     traffic_generator_node: TGNodeConfiguration\n     vdevs: list[str]\n-    skip_smoke_tests: bool\n \n     @staticmethod\n     def from_dict(\n-        d: dict, node_map: dict[str, Union[SutNodeConfiguration | TGNodeConfiguration]]\n+        d: ExecutionConfigDict,\n+        node_map: dict[str, Union[SutNodeConfiguration | TGNodeConfiguration]],\n     ) -> \"ExecutionConfiguration\":\n+        \"\"\"A convenience method that processes the inputs before creating an instance.\n+\n+        The build target and the test suite config is transformed into their respective objects.\n+        SUT and TG configuration are taken from `node_map`. The other (:class:`bool`) attributes are\n+        just stored.\n+\n+        Args:\n+            d: The configuration dictionary.\n+            node_map: A dictionary mapping node names to their config objects.\n+\n+        Returns:\n+            The execution configuration instance.\n+        \"\"\"\n         build_targets: list[BuildTargetConfiguration] = list(\n             map(BuildTargetConfiguration.from_dict, d[\"build_targets\"])\n         )\n@@ -291,10 +517,31 @@ def from_dict(\n \n @dataclass(slots=True, frozen=True)\n class Configuration:\n+    \"\"\"DTS testbed and test configuration.\n+\n+    The node configuration is not stored in this object. Rather, all used node configurations\n+    are stored inside the execution configuration where the nodes are actually used.\n+\n+    Attributes:\n+        executions: Execution configurations.\n+    \"\"\"\n+\n     executions: list[ExecutionConfiguration]\n \n     @staticmethod\n-    def from_dict(d: dict) -> \"Configuration\":\n+    def from_dict(d: ConfigurationDict) -> \"Configuration\":\n+        \"\"\"A convenience method that processes the inputs before creating an instance.\n+\n+        Build target and test suite config is transformed into their respective objects.\n+        SUT and TG configuration are taken from `node_map`. The other (:class:`bool`) attributes are\n+        just stored.\n+\n+        Args:\n+            d: The configuration dictionary.\n+\n+        Returns:\n+            The whole configuration instance.\n+        \"\"\"\n         nodes: list[Union[SutNodeConfiguration | TGNodeConfiguration]] = list(\n             map(NodeConfiguration.from_dict, d[\"nodes\"])\n         )\n@@ -313,9 +560,17 @@ def from_dict(d: dict) -> \"Configuration\":\n \n \n def load_config() -> Configuration:\n-    \"\"\"\n-    Loads the configuration file and the configuration file schema,\n-    validates the configuration file, and creates a configuration object.\n+    \"\"\"Load DTS test run configuration from a file.\n+\n+    Load the YAML test run configuration file\n+    and :download:`the configuration file schema <conf_yaml_schema.json>`,\n+    validate the test run configuration file, and create a test run configuration object.\n+\n+    The YAML test run configuration file is specified in the :option:`--config-file` command line\n+    argument or the :envvar:`DTS_CFG_FILE` environment variable.\n+\n+    Returns:\n+        The parsed test run configuration.\n     \"\"\"\n     with open(SETTINGS.config_file_path, \"r\") as f:\n         config_data = yaml.safe_load(f)\n@@ -326,6 +581,8 @@ def load_config() -> Configuration:\n \n     with open(schema_path, \"r\") as f:\n         schema = json.load(f)\n-    config: dict[str, Any] = warlock.model_factory(schema, name=\"_Config\")(config_data)\n-    config_obj: Configuration = Configuration.from_dict(dict(config))\n+    config = warlock.model_factory(schema, name=\"_Config\")(config_data)\n+    config_obj: Configuration = Configuration.from_dict(\n+        dict(config)  # type: ignore[arg-type]\n+    )\n     return config_obj\ndiff --git a/dts/framework/config/types.py b/dts/framework/config/types.py\nnew file mode 100644\nindex 0000000000..1927910d88\n--- /dev/null\n+++ b/dts/framework/config/types.py\n@@ -0,0 +1,132 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2023 PANTHEON.tech s.r.o.\n+\n+\"\"\"Configuration dictionary contents specification.\n+\n+These type definitions serve as documentation of the configuration dictionary contents.\n+\n+The definitions use the built-in :class:`~typing.TypedDict` construct.\n+\"\"\"\n+\n+from typing import TypedDict\n+\n+\n+class PortConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    pci: str\n+    #:\n+    os_driver_for_dpdk: str\n+    #:\n+    os_driver: str\n+    #:\n+    peer_node: str\n+    #:\n+    peer_pci: str\n+\n+\n+class TrafficGeneratorConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    type: str\n+\n+\n+class HugepageConfigurationDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    amount: int\n+    #:\n+    force_first_numa: bool\n+\n+\n+class NodeConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    hugepages: HugepageConfigurationDict\n+    #:\n+    name: str\n+    #:\n+    hostname: str\n+    #:\n+    user: str\n+    #:\n+    password: str\n+    #:\n+    arch: str\n+    #:\n+    os: str\n+    #:\n+    lcores: str\n+    #:\n+    use_first_core: bool\n+    #:\n+    ports: list[PortConfigDict]\n+    #:\n+    memory_channels: int\n+    #:\n+    traffic_generator: TrafficGeneratorConfigDict\n+\n+\n+class BuildTargetConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    arch: str\n+    #:\n+    os: str\n+    #:\n+    cpu: str\n+    #:\n+    compiler: str\n+    #:\n+    compiler_wrapper: str\n+\n+\n+class TestSuiteConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    suite: str\n+    #:\n+    cases: list[str]\n+\n+\n+class ExecutionSUTConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    node_name: str\n+    #:\n+    vdevs: list[str]\n+\n+\n+class ExecutionConfigDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    build_targets: list[BuildTargetConfigDict]\n+    #:\n+    perf: bool\n+    #:\n+    func: bool\n+    #:\n+    skip_smoke_tests: bool\n+    #:\n+    test_suites: TestSuiteConfigDict\n+    #:\n+    system_under_test_node: ExecutionSUTConfigDict\n+    #:\n+    traffic_generator_node: str\n+\n+\n+class ConfigurationDict(TypedDict):\n+    \"\"\"Allowed keys and values.\"\"\"\n+\n+    #:\n+    nodes: list[NodeConfigDict]\n+    #:\n+    executions: list[ExecutionConfigDict]\n",
    "prefixes": [
        "v5",
        "10/23"
    ]
}