DPDK patches and discussions
 help / color / mirror / Atom feed
From: Luca Vizzarro <Luca.Vizzarro@arm.com>
To: Andrew Bailey <abailey@iol.unh.edu>
Cc: dev@dpdk.org, dmarx@iol.unh.edu, probb@iol.unh.edu
Subject: Re: [PATCH v2] dts: type hints updated for method arguments and return types
Date: Fri, 8 Aug 2025 19:21:12 +0100	[thread overview]
Message-ID: <9e6a703e-7662-401a-a318-235bdc0bd937@arm.com> (raw)
In-Reply-To: <20250806162900.273571-1-abailey@iol.unh.edu>

Hi Andrew,

thank you for your v2! Based on the contributing guidelines [1], any 
further version should be posted in reply to the original cover letter 
if sent, otherwise the very first patch. Please review these before you 
send the next patches.

A cover letter email is useful when there is more than one patch, but 
this is not the case. You can add a cover letter (if needed) below the 
commit description, the contributing guidelines describe this in chapter 
9.8. The most important purpose of the cover letter is to describe the 
changes you made since the last version you sent.

Still, based on the contributing guidelines the commit subject is to be 
in imperative form and concise. The original commit subject was fine, 
you had to change it to include the arguments. To keep it imperative and 
concise consider:

   dts: add missing type hints to method signatures

I notice the relevant change to .mailmap is still missing. This is 
required in order to accept any contributions.

On 06/08/2025 17:29, Andrew Bailey wrote:
> diff --git a/dts/framework/context.py b/dts/framework/context.py
> index 4360bc8699..ba2fdf55b3 100644
> --- a/dts/framework/context.py
> +++ b/dts/framework/context.py
> @@ -4,12 +4,14 @@
>   """Runtime contexts."""
>   
>   import functools
> +from collections.abc import Callable
>   from dataclasses import MISSING, dataclass, field, fields
> -from typing import TYPE_CHECKING, ParamSpec
> +from typing import TYPE_CHECKING, ParamSpec, Type

I am wondering if you are using a wrong version of Python? In Python 
3.12, which is the version we are supporting, `type` (lowercase t) is a 
built-in keyword, and doesn't require being imported.

>   
>   from framework.exception import InternalError
>   from framework.remote_session.shell_pool import ShellPool
>   from framework.settings import SETTINGS
> +from framework.testbed_model.capability import TestProtocol
>   from framework.testbed_model.cpu import LogicalCoreCount, LogicalCoreList
>   from framework.testbed_model.node import Node
>   from framework.testbed_model.topology import Topology
> @@ -97,12 +99,12 @@ def init_ctx(ctx: Context) -> None:
>   
>   def filter_cores(
>       specifier: LogicalCoreCount | LogicalCoreList, ascending_cores: bool | None = None
> -):
> +) -> Callable[[Callable], Callable[[], Type[TestProtocol]]]:
>       """Decorates functions that require a temporary update to the lcore specifier."""
>   
> -    def decorator(func):
> +    def decorator(func: Callable) -> Callable[[], Type[TestProtocol]]:
>           @functools.wraps(func)
> -        def wrapper(*args: P.args, **kwargs: P.kwargs):
> +        def wrapper(*args: P.args, **kwargs: P.kwargs) -> Type[TestProtocol]:

This is not returning TestProtocol. Here func is type[TestProtocol], so 
the return type here is whatetever `type[TestProtocol]` returns when 
called. I took some time to look into this one and unfortunately it's 
not as straightforward as I hoped. I think as a compromised you could 
use Any in the end. The signatures should look like:

   filter_cores(...) -> Callable[[type[TestProtocol]], Callable]
   decorator(func: type[TestProtocol]) -> Callable:
   wrapper(...) -> Any

Doing it this way we can enforce through mypy that the decorator is only 
used with test suites and cases.

>               local_ctx = get_ctx().local
>   
>               old_specifier = local_ctx.lcore_filter_specifier
> diff --git a/dts/framework/logger.py b/dts/framework/logger.py
> index f43b442bc9..fe5737f285 100644
> --- a/dts/framework/logger.py
> +++ b/dts/framework/logger.py
> @@ -15,7 +15,7 @@
>   import logging
>   from logging import FileHandler, StreamHandler
>   from pathlib import Path
> -from typing import ClassVar
> +from typing import Any, ClassVar
>   
>   date_fmt = "%Y/%m/%d %H:%M:%S"
>   stream_fmt = "%(asctime)s - %(stage)s - %(name)s - %(levelname)s - %(message)s"
> @@ -36,12 +36,12 @@ class DTSLogger(logging.Logger):
>       _stage: ClassVar[str] = "pre_run"
>       _extra_file_handlers: list[FileHandler] = []
>   
> -    def __init__(self, *args, **kwargs):
> +    def __init__(self, *args: str, **kwargs: str) -> None:
we don't know they are str, should be Any.>           """Extend the 
constructor with extra file handlers."""
>           self._extra_file_handlers = []
>           super().__init__(*args, **kwargs)
>   
> -    def makeRecord(self, *args, **kwargs) -> logging.LogRecord:
> +    def makeRecord(self, *args: Any, **kwargs: Any) -> logging.LogRecord:
>           """Generates a record with additional stage information.
>   
>           This is the default method for the :class:`~logging.Logger` class. We extend it
> diff --git a/dts/framework/params/__init__.py b/dts/framework/params/__init__.py
> index 1ae227d7b4..03476681ee 100644
> --- a/dts/framework/params/__init__.py
> +++ b/dts/framework/params/__init__.py
> @@ -44,7 +44,7 @@ def _reduce_functions(funcs: Iterable[FnPtr]) -> FnPtr:
>           FnPtr: A function that calls the given functions from left to right.
>       """
>   
> -    def reduced_fn(value):
> +    def reduced_fn(value: Any) -> Any:
looks like FnPtr should be using the generic T, in which case this can 
be reduced_fn(value: T) -> T. And FnPtr = Callable[[T], T]

And this made me realise you missed _class_decorator under modify_str

>           for fn in funcs:
>               value = fn(value)
>           return value
> diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
> index 606d6e22fe..6a0a8911c9 100644
> --- a/dts/framework/remote_session/dpdk.py
> +++ b/dts/framework/remote_session/dpdk.py
> @@ -62,7 +62,7 @@ class DPDKBuildEnvironment:
> @@ -155,7 +155,7 @@ def _copy_dpdk_tree(self, dpdk_tree_path: Path) -> None:
>               dpdk_tree_path,
>               self.remote_dpdk_tree_path,
>               exclude=[".git", "*.o"],
> -            compress_format=TarCompressionFormat.gzip,
> +            compress_format=TarCompressionFormat.gz,
This is out of the scope of this patch. Shouldn't be changed.>           )
>   
>       def _validate_remote_dpdk_tarball(self, dpdk_tarball: PurePath) -> None:
> diff --git a/dts/framework/remote_session/testpmd_shell.py b/dts/framework/remote_session/testpmd_shell.py
> index ad8cb273dc..c80de55d34 100644
> --- a/dts/framework/remote_session/testpmd_shell.py
> +++ b/dts/framework/remote_session/testpmd_shell.py
> @@ -90,7 +99,7 @@ class VLANOffloadFlag(Flag):
>       QINQ_STRIP = auto()
>   
>       @classmethod
> -    def from_str_dict(cls, d):
> +    def from_str_dict(cls, d: dict) -> Self:
we expect it to be dict[str, str] like the docstring suggests.> 
  """Makes an instance from a dict containing the flag member names with 
an "on" value.
>   
>           Args:
> @@ -1449,7 +1458,7 @@ def requires_stopped_ports(func: TestPmdShellMethod) -> TestPmdShellMethod:
>       """
>   
>       @functools.wraps(func)
> -    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs):
> +    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs) -> TestPmdShellMethod:
This does not return a method, it returns whatever the method returns 
when it's called. Like I suggested in v1, you should be able to 
replicate this with a generic R variable (like for @only_active). I 
recognise now that I have been experimenting that this is really flaky 
and mypy is not interacting well with this. Please just use Any.> 
    if self.ports_started:
>               self._logger.debug("Ports need to be stopped to continue.")
>               self.stop_all_ports()
> @@ -1470,7 +1479,7 @@ def requires_started_ports(func: TestPmdShellMethod) -> TestPmdShellMethod:
>       """
>   
>       @functools.wraps(func)
> -    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs):
> +    def _wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs) -> TestPmdShellMethod:
as above>           if not self.ports_started:
>               self._logger.debug("Ports need to be started to continue.")
>               self.start_all_ports()
> @@ -1492,7 +1501,7 @@ def add_remove_mtu(mtu: int = 1500) -> Callable[[TestPmdShellMethod], TestPmdShe
>   
>       def decorator(func: TestPmdShellMethod) -> TestPmdShellMethod:
>           @functools.wraps(func)
> -        def wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs):
> +        def wrapper(self: "TestPmdShell", *args: P.args, **kwargs: P.kwargs) -> TestPmdShellMethod:
as above>               original_mtu = self.ports[0].mtu
>               self.set_port_mtu_all(mtu=mtu, verify=False)
>               retval = func(self, *args, **kwargs)
> diff --git a/dts/framework/testbed_model/capability.py b/dts/framework/testbed_model/capability.py
> index f895b22bb3..db5753405b 100644
> --- a/dts/framework/testbed_model/capability.py
> +++ b/dts/framework/testbed_model/capability.py
> @@ -385,7 +385,7 @@ def __eq__(self, other) -> bool:
>           """
>           return self.topology_type == other.topology_type
>   
> -    def __lt__(self, other) -> bool:
> +    def __lt__(self, other: Self) -> bool:
Unfortunately, we cannot use Self here because realistically other can 
be Any.>           """Compare the 
:attr:`~TopologyCapability.topology_type`s.
>   
>           Args:
> @@ -396,7 +396,7 @@ def __lt__(self, other) -> bool:
>           """
>           return self.topology_type < other.topology_type
>   
> -    def __gt__(self, other) -> bool:
> +    def __gt__(self, other: Self) -> bool:
as above>           """Compare the 
:attr:`~TopologyCapability.topology_type`s.
>   
>           Args:
> @@ -407,7 +407,7 @@ def __gt__(self, other) -> bool:
>           """
>           return other < self
>   
> -    def __le__(self, other) -> bool:
> +    def __le__(self, other: Self) -> bool:
as above>           """Compare the 
:attr:`~TopologyCapability.topology_type`s.
>   
>           Args:
> diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py
> index b6e03aa83d..de507ffe6a 100644
> --- a/dts/framework/testbed_model/os_session.py
> +++ b/dts/framework/testbed_model/os_session.py
> @@ -115,7 +115,7 @@ def __init__(
>           node_config: NodeConfiguration,
>           name: str,
>           logger: DTSLogger,
> -    ):
> +    ) -> None:
>           """Initialize the OS-aware session.
>   
>           Connect to the node right away and also create an interactive remote session.
> @@ -266,7 +266,7 @@ def copy_dir_from(
>           self,
>           source_dir: str | PurePath,
>           destination_dir: str | Path,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> None:
>           """Copy a directory from the remote node to the local filesystem.
> @@ -306,7 +306,7 @@ def copy_dir_to(
>           self,
>           source_dir: str | Path,
>           destination_dir: str | PurePath,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> None:
>           """Copy a directory from the local filesystem to the remote node.
> @@ -375,7 +375,7 @@ def remove_remote_dir(
>       def create_remote_tarball(
>           self,
>           remote_dir_path: str | PurePath,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> PurePosixPath:
>           """Create a tarball from the contents of the specified remote directory.
> diff --git a/dts/framework/testbed_model/posix_session.py b/dts/framework/testbed_model/posix_session.py
> index c71bc93f0b..992017f113 100644
> --- a/dts/framework/testbed_model/posix_session.py
> +++ b/dts/framework/testbed_model/posix_session.py
> @@ -15,6 +15,7 @@
>   import re
>   from collections.abc import Iterable
>   from pathlib import Path, PurePath, PurePosixPath
> +from typing import Any
>   
>   from framework.exception import DPDKBuildError, RemoteCommandExecutionError
>   from framework.settings import SETTINGS
> @@ -116,7 +117,7 @@ def copy_dir_from(
>           self,
>           source_dir: str | PurePath,
>           destination_dir: str | Path,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> None:
>           """Overrides :meth:`~.os_session.OSSession.copy_dir_from`."""
> @@ -135,7 +136,7 @@ def copy_dir_to(
>           self,
>           source_dir: str | Path,
>           destination_dir: str | PurePath,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> None:
>           """Overrides :meth:`~.os_session.OSSession.copy_dir_to`."""
> @@ -178,12 +179,12 @@ def remove_remote_dir(
>       def create_remote_tarball(
>           self,
>           remote_dir_path: str | PurePath,
> -        compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +        compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope change>           exclude: str | list[str] | None = None,
>       ) -> PurePosixPath:
>           """Overrides :meth:`~.os_session.OSSession.create_remote_tarball`."""
>   
> -        def generate_tar_exclude_args(exclude_patterns) -> str:
> +        def generate_tar_exclude_args(exclude_patterns: Any) -> str:
based on context this should be the same exact type signature as the 
`exclude` argument in the outer function.>               """Generate 
args to exclude patterns when creating a tarball.
>   
>               Args:
> diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py b/dts/framework/testbed_model/traffic_generator/scapy.py
> index e21ba4ed96..40030abe7b 100644
> --- a/dts/framework/testbed_model/traffic_generator/scapy.py
> +++ b/dts/framework/testbed_model/traffic_generator/scapy.py
> @@ -296,7 +296,7 @@ class also extends :class:`.capturing_traffic_generator.CapturingTrafficGenerato
>       #: Padding to add to the start of a line for python syntax compliance.
>       _python_indentation: ClassVar[str] = " " * 4
>   
> -    def __init__(self, tg_node: Node, config: ScapyTrafficGeneratorConfig, **kwargs):
> +    def __init__(self, tg_node: Node, config: ScapyTrafficGeneratorConfig, **kwargs) -> None:
missed kwargs, Any can go here>           """Extend the constructor with 
Scapy TG specifics.
>   
>           Initializes both the traffic generator and the interactive shell used to handle Scapy
> diff --git a/dts/framework/testbed_model/traffic_generator/traffic_generator.py b/dts/framework/testbed_model/traffic_generator/traffic_generator.py
> index 8f53b07daf..8b5fa98b89 100644
> --- a/dts/framework/testbed_model/traffic_generator/traffic_generator.py
> +++ b/dts/framework/testbed_model/traffic_generator/traffic_generator.py
> @@ -34,7 +34,9 @@ class TrafficGenerator(ABC):
>       _tg_node: Node
>       _logger: DTSLogger
>   
> -    def __init__(self, tg_node: Node, config: TrafficGeneratorConfig, **kwargs):
> +    def __init__(
> +        self, tg_node: Node, config: TrafficGeneratorConfig, **kwargs: Node | TrafficGeneratorConfig
kwargs is actually unused here, so it can be removed. Otherwise we 
cannot make assumption on what it is, so it should be Any> +    ) -> None:
>           """Initialize the traffic generator.
>   
>           Additional keyword arguments can be passed through `kwargs` if needed for fulfilling other
> diff --git a/dts/framework/utils.py b/dts/framework/utils.py
> index 0c81ab1b95..51f9a3c541 100644
> --- a/dts/framework/utils.py
> +++ b/dts/framework/utils.py
> @@ -19,7 +19,7 @@
>   import os
>   import random
>   import tarfile
> -from enum import Enum, Flag
> +from enum import Enum, Flag, auto
not needed as out of scope>   from pathlib import Path
>   from typing import Any, Callable
>   
> @@ -136,15 +136,15 @@ class TarCompressionFormat(StrEnum):
>       Its value is set to 'tar' to indicate that the file is an uncompressed tar archive.
>       """
>   
> -    none = "tar"
> -    gzip = "gz"
> -    compress = "Z"
> -    bzip2 = "bz2"
> -    lzip = "lz"
> -    lzma = "lzma"
> -    lzop = "lzo"
> -    xz = "xz"
> -    zstd = "zst"
> +    tar = auto()
> +    gz = auto()
> +    Z = auto()
> +    bz2 = auto()
> +    lz = auto()
> +    lzma = auto()
> +    lzo = auto()
> +    xz = auto()
> +    zst = auto()

I am really confused why you made this change, given it's not connected 
in any way to this commit. That point aside, this is a counterintuitive 
change. The original approach mapped common compression algorithm names 
to their corresponding file extensions. When we talk about compression 
algorithms we don't talk about them based on their extension.

>   
>       @property
>       def extension(self):
> @@ -154,7 +154,7 @@ def extension(self):
>           For other compression formats, the extension will be in the format
>           'tar.{compression format}'.
>           """
> -        return f"{self.value}" if self == self.none else f"{self.none.value}.{self.value}"
> +        return f"{self.value}" if self == self.tar else f"{self.tar.value}.{self.value}"
out of scope>
>   
>   def convert_to_list_of_string(value: Any | list[Any]) -> list[str]:
> @@ -164,7 +164,7 @@ def convert_to_list_of_string(value: Any | list[Any]) -> list[str]:
>   
>   def create_tarball(
>       dir_path: Path,
> -    compress_format: TarCompressionFormat = TarCompressionFormat.none,
> +    compress_format: TarCompressionFormat = TarCompressionFormat.tar,
out of scope>       exclude: Any | list[Any] | None = None,
>   ) -> Path:
>       """Create a tarball from the contents of the specified directory.

[1] https://doc.dpdk.org/guides/contributing/patches.html

  reply	other threads:[~2025-08-08 18:21 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-06 16:29 Andrew Bailey
2025-08-08 18:21 ` Luca Vizzarro [this message]
2025-08-08 20:49   ` Andrew Bailey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9e6a703e-7662-401a-a318-235bdc0bd937@arm.com \
    --to=luca.vizzarro@arm.com \
    --cc=abailey@iol.unh.edu \
    --cc=dev@dpdk.org \
    --cc=dmarx@iol.unh.edu \
    --cc=probb@iol.unh.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).