Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 0/2] tools: ynl: pyynl: minor library ease of use improvements
@ 2026-06-30  0:14 Jakub Kicinski
  2026-06-30  0:14 ` [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root Jakub Kicinski
  2026-06-30  0:14 ` [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib Jakub Kicinski
  0 siblings, 2 replies; 6+ messages in thread
From: Jakub Kicinski @ 2026-06-30  0:14 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, andrew+netdev, horms, donald.hunter,
	sdf, gal, jstancek, ast, Jakub Kicinski

A couple of improvements for using pyynl. I was writing some scripts
to check / set up things in NIPA over the merge window, and wished
pyynl was easier to bootstrap.

Jakub Kicinski (2):
  tools: ynl: pyynl: re-export the library API from the package root
  tools: ynl: pyynl: pull the --family resolution logic into the lib

 tools/net/ynl/pyynl/__init__.py     |  9 +++++
 tools/net/ynl/pyynl/cli.py          | 53 +++++------------------------
 tools/net/ynl/pyynl/lib/__init__.py |  3 +-
 tools/net/ynl/pyynl/lib/nlspec.py   | 14 +++++++-
 tools/net/ynl/pyynl/lib/specdir.py  | 51 +++++++++++++++++++++++++++
 tools/net/ynl/pyynl/lib/ynl.py      | 19 +++++++++--
 6 files changed, 100 insertions(+), 49 deletions(-)
 create mode 100644 tools/net/ynl/pyynl/lib/specdir.py

-- 
2.54.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root
  2026-06-30  0:14 [PATCH net-next 0/2] tools: ynl: pyynl: minor library ease of use improvements Jakub Kicinski
@ 2026-06-30  0:14 ` Jakub Kicinski
  2026-06-30  7:28   ` Jan Stancek
  2026-06-30  8:38   ` Donald Hunter
  2026-06-30  0:14 ` [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib Jakub Kicinski
  1 sibling, 2 replies; 6+ messages in thread
From: Jakub Kicinski @ 2026-06-30  0:14 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, andrew+netdev, horms, donald.hunter,
	sdf, gal, jstancek, ast, Jakub Kicinski

The public classes live in pyynl.lib, so users had to spell out

  from pyynl.lib import YnlFamily

which I forget at least once a month. Re-export lib's API from
the package __init__ so that

  from pyynl import YnlFamily

works as well. I don't think there was a real reason not to do
this?

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/net/ynl/pyynl/__init__.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py
index e69de29bb2d1..d8f59c132ab7 100644
--- a/tools/net/ynl/pyynl/__init__.py
+++ b/tools/net/ynl/pyynl/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+""" Python YNL (YAML Netlink) library. """
+
+# Re-export the public library API so it can be imported straight from the
+# package, e.g. `from pyynl import YnlFamily`.
+# pylint: disable=wildcard-import,unused-wildcard-import
+from .lib import *
+from .lib import __all__
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib
  2026-06-30  0:14 [PATCH net-next 0/2] tools: ynl: pyynl: minor library ease of use improvements Jakub Kicinski
  2026-06-30  0:14 ` [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root Jakub Kicinski
@ 2026-06-30  0:14 ` Jakub Kicinski
  2026-06-30 10:10   ` Donald Hunter
  1 sibling, 1 reply; 6+ messages in thread
From: Jakub Kicinski @ 2026-06-30  0:14 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, andrew+netdev, horms, donald.hunter,
	sdf, gal, jstancek, ast, Jakub Kicinski

When packaging YNL as a system level utility we added a --family
argument which auto-resolves the full spec path from a well known
path in /usr/share. Spelling out full YAML spec files is at this
point only done in-tree, for example in the selftests which need
the very latest YAML. But the selftests have their own wrapping
classes for each family so test authors aren't really bothered
by having to spell the paths out.

Afford the same ease of use to the Python library users.
Move the path resolution from the CLI code to the library.
This simplifies the pyynl use by a lot:

  from pyynl import YnlFamily

  ynl = YnlFamily(family="netdev")

Unless I'm missing a trick, resolving the /usr/share path
is hard enough for most users to lean towards shelling out
to ynl CLI with --output-json, which is sad.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/net/ynl/pyynl/cli.py          | 53 +++++------------------------
 tools/net/ynl/pyynl/lib/__init__.py |  3 +-
 tools/net/ynl/pyynl/lib/nlspec.py   | 14 +++++++-
 tools/net/ynl/pyynl/lib/specdir.py  | 51 +++++++++++++++++++++++++++
 tools/net/ynl/pyynl/lib/ynl.py      | 19 +++++++++--
 5 files changed, 91 insertions(+), 49 deletions(-)
 create mode 100644 tools/net/ynl/pyynl/lib/specdir.py

diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py
index 8275a806cf73..05e422d4709e 100755
--- a/tools/net/ynl/pyynl/cli.py
+++ b/tools/net/ynl/pyynl/cli.py
@@ -17,9 +17,7 @@ import textwrap
 # pylint: disable=no-name-in-module,wrong-import-position
 sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
 from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException
-
-SYS_SCHEMA_DIR='/usr/share/ynl'
-RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
+from lib import list_families
 
 # pylint: disable=too-few-public-methods,too-many-locals
 class Colors:
@@ -48,30 +46,6 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
     """ Get terminal width in columns (80 if stdout is not a terminal) """
     return shutil.get_terminal_size().columns
 
-def schema_dir():
-    """
-    Return the effective schema directory, preferring in-tree before
-    system schema directory.
-    """
-    script_dir = os.path.dirname(os.path.abspath(__file__))
-    schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}")
-    if not os.path.isdir(schema_dir_):
-        schema_dir_ = SYS_SCHEMA_DIR
-    if not os.path.isdir(schema_dir_):
-        raise YnlException(f"Schema directory {schema_dir_} does not exist")
-    return schema_dir_
-
-def spec_dir():
-    """
-    Return the effective spec directory, relative to the effective
-    schema directory.
-    """
-    spec_dir_ = schema_dir() + '/specs'
-    if not os.path.isdir(spec_dir_):
-        raise YnlException(f"Spec directory {spec_dir_} does not exist")
-    return spec_dir_
-
-
 class YnlEncoder(json.JSONEncoder):
     """A custom encoder for emitting JSON with ynl-specific instance types"""
     def default(self, o):
@@ -272,9 +246,8 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
             pprint.pprint(msg, width=term_width(), compact=True)
 
     if args.list_families:
-        for filename in sorted(os.listdir(spec_dir())):
-            if filename.endswith('.yaml'):
-                print(filename.removesuffix('.yaml'))
+        for family in list_families():
+            print(family)
         return
 
     if args.no_schema:
@@ -284,28 +257,20 @@ RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
     if args.json_text:
         attrs = json.loads(args.json_text)
 
-    if args.family:
-        spec = f"{spec_dir()}/{args.family}.yaml"
-    else:
-        spec = args.spec
-    if not os.path.isfile(spec):
-        raise YnlException(f"Spec file {spec} does not exist")
+    if args.spec and not os.path.isfile(args.spec):
+        raise YnlException(f"Spec file {args.spec} does not exist")
 
+    # Spec/YnlFamily will raise if both or neither spec and family are given
     if args.validate:
         try:
-            SpecFamily(spec, args.schema)
+            SpecFamily(args.spec, schema_path=args.schema, family=args.family)
         except SpecException as error:
             print(error)
             sys.exit(1)
         return
 
-    if args.family: # set behaviour when using installed specs
-        if args.schema is None and spec.startswith(SYS_SCHEMA_DIR):
-            args.schema = '' # disable schema validation when installed
-        if args.process_unknown is None:
-            args.process_unknown = True
-
-    ynl = YnlFamily(spec, args.schema, args.process_unknown,
+    ynl = YnlFamily(args.spec, schema=args.schema, family=args.family,
+                    process_unknown=args.process_unknown,
                     recv_size=args.dbg_small_recv)
     if args.dbg_small_recv:
         ynl.set_recv_dbg(True)
diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py
index be741985ae4e..aa4263c8cba9 100644
--- a/tools/net/ynl/pyynl/lib/__init__.py
+++ b/tools/net/ynl/pyynl/lib/__init__.py
@@ -5,12 +5,13 @@
 from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \
     SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \
     SpecException
+from .specdir import list_families
 from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException
 
 from .doc_generator import YnlDocGenerator
 
 __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet",
            "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat",
-           "SpecException",
+           "SpecException", "list_families",
            "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException",
            "YnlDocGenerator"]
diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py
index 0469a0e270d0..2407271fb7f7 100644
--- a/tools/net/ynl/pyynl/lib/nlspec.py
+++ b/tools/net/ynl/pyynl/lib/nlspec.py
@@ -12,6 +12,8 @@ import importlib
 import os
 import yaml as pyyaml
 
+from .specdir import find_spec, SYS_SCHEMA_DIR
+
 
 class SpecException(Exception):
     """Netlink spec exception.
@@ -444,7 +446,17 @@ import yaml as pyyaml
     except AttributeError:
         _yaml_loader = pyyaml.SafeLoader
 
-    def __init__(self, spec_path, schema_path=None, exclude_ops=None):
+    def __init__(self, spec_path=None, schema_path=None, exclude_ops=None,
+                 family=None):
+        if (spec_path is None) == (family is None):
+            raise ValueError("Specify exactly one of spec path or family name")
+        if family is not None:
+            spec_path = find_spec(family)
+            # Installed specs are assumed correct, so skip schema validation
+            # to save cycles; only validate in the development environment.
+            if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR):
+                schema_path = ''
+
         with open(spec_path, "r", encoding='utf-8') as stream:
             prefix = '# SPDX-License-Identifier: '
             first = stream.readline().strip()
diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py
new file mode 100644
index 000000000000..fcea9b9fb7b0
--- /dev/null
+++ b/tools/net/ynl/pyynl/lib/specdir.py
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+"""
+Locating YNL spec and schema files on disk.
+
+Resolves the directory holding the YAML specs (preferring an in-tree copy
+over the installed system path) and maps family names to spec files.
+"""
+
+import os
+
+SYS_SCHEMA_DIR='/usr/share/ynl'
+RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink'
+
+
+def schema_dir():
+    """
+    Return the effective schema directory, preferring in-tree before
+    system schema directory.
+    """
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}")
+    if not os.path.isdir(schema_dir_):
+        schema_dir_ = SYS_SCHEMA_DIR
+    if not os.path.isdir(schema_dir_):
+        raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist")
+    return schema_dir_
+
+def spec_dir():
+    """
+    Return the effective spec directory, relative to the effective
+    schema directory.
+    """
+    spec_dir_ = schema_dir() + '/specs'
+    if not os.path.isdir(spec_dir_):
+        raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist")
+    return spec_dir_
+
+
+def find_spec(family):
+    """ Return the path to the YAML spec file for a family by name. """
+    spec = f"{spec_dir()}/{family}.yaml"
+    if not os.path.isfile(spec):
+        raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}")
+    return spec
+
+
+def list_families():
+    """ Return the sorted names of all families with an installed spec. """
+    return sorted(f.removesuffix('.yaml')
+                  for f in os.listdir(spec_dir()) if f.endswith('.yaml'))
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 092d132edec1..8682bf588e1f 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -661,6 +661,14 @@ from .nlspec import SpecFamily
     """
     YNL family -- a Netlink interface built from a YAML spec.
 
+    The spec can be selected either by file path (def_path=) or, when it
+    ships in a well-known location, by family name (family="xyz"); exactly
+    one of the two must be given. For example:
+
+      from pyynl import YnlFamily
+
+      ynl = YnlFamily(family="netdev")
+
     Primary use of the class is to execute Netlink commands:
 
       ynl.<op_name>(attrs, ...)
@@ -691,11 +699,16 @@ from .nlspec import SpecFamily
 
       ynl.get_policy(op_name, mode)      -- query kernel policy for an op
     """
-    def __init__(self, def_path, schema=None, process_unknown=False,
-                 recv_size=0):
-        super().__init__(def_path, schema)
+    def __init__(self, def_path=None, schema=None, process_unknown=None,
+                 recv_size=0, family=None):
+        super().__init__(def_path, schema, family=family)
 
         self.include_raw = False
+        # Specs from /usr (selected by family=) have a higher chance of being
+        # stale, default to ignoring unknown attrs. In-tree users, and users
+        # who bundle the spec need to make a conscious decision.
+        if process_unknown is None:
+            process_unknown = family is not None
         self.process_unknown = process_unknown
 
         try:
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root
  2026-06-30  0:14 ` [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root Jakub Kicinski
@ 2026-06-30  7:28   ` Jan Stancek
  2026-06-30  8:38   ` Donald Hunter
  1 sibling, 0 replies; 6+ messages in thread
From: Jan Stancek @ 2026-06-30  7:28 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms,
	donald.hunter, sdf, gal, ast

On Tue, Jun 30, 2026 at 2:20 AM Jakub Kicinski <kuba@kernel.org> wrote:
>
> The public classes live in pyynl.lib, so users had to spell out
>
>   from pyynl.lib import YnlFamily
>
> which I forget at least once a month. Re-export lib's API from
> the package __init__ so that
>
>   from pyynl import YnlFamily
>
> works as well. I don't think there was a real reason not to do
> this?

I think it was more uncertainty on my part whether in future we get
more subdirectories.

Acked-by: Jan Stancek <jstancek@redhat.com>

>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/net/ynl/pyynl/__init__.py | 9 +++++++++
>  1 file changed, 9 insertions(+)
>
> diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py
> index e69de29bb2d1..d8f59c132ab7 100644
> --- a/tools/net/ynl/pyynl/__init__.py
> +++ b/tools/net/ynl/pyynl/__init__.py
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
> +
> +""" Python YNL (YAML Netlink) library. """
> +
> +# Re-export the public library API so it can be imported straight from the
> +# package, e.g. `from pyynl import YnlFamily`.
> +# pylint: disable=wildcard-import,unused-wildcard-import
> +from .lib import *
> +from .lib import __all__
> --
> 2.54.0
>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root
  2026-06-30  0:14 ` [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root Jakub Kicinski
  2026-06-30  7:28   ` Jan Stancek
@ 2026-06-30  8:38   ` Donald Hunter
  1 sibling, 0 replies; 6+ messages in thread
From: Donald Hunter @ 2026-06-30  8:38 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, sdf, gal,
	jstancek, ast

Jakub Kicinski <kuba@kernel.org> writes:

> The public classes live in pyynl.lib, so users had to spell out
>
>   from pyynl.lib import YnlFamily
>
> which I forget at least once a month. Re-export lib's API from
> the package __init__ so that
>
>   from pyynl import YnlFamily
>
> works as well. I don't think there was a real reason not to do
> this?
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>

Reviewed-by: Donald Hunter <donald.hunter@gmail.com>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib
  2026-06-30  0:14 ` [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib Jakub Kicinski
@ 2026-06-30 10:10   ` Donald Hunter
  0 siblings, 0 replies; 6+ messages in thread
From: Donald Hunter @ 2026-06-30 10:10 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, sdf, gal,
	jstancek, ast

Jakub Kicinski <kuba@kernel.org> writes:

> When packaging YNL as a system level utility we added a --family
> argument which auto-resolves the full spec path from a well known
> path in /usr/share. Spelling out full YAML spec files is at this
> point only done in-tree, for example in the selftests which need
> the very latest YAML. But the selftests have their own wrapping
> classes for each family so test authors aren't really bothered
> by having to spell the paths out.
>
> Afford the same ease of use to the Python library users.
> Move the path resolution from the CLI code to the library.
> This simplifies the pyynl use by a lot:
>
>   from pyynl import YnlFamily
>
>   ynl = YnlFamily(family="netdev")
>
> Unless I'm missing a trick, resolving the /usr/share path
> is hard enough for most users to lean towards shelling out
> to ynl CLI with --output-json, which is sad.
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/net/ynl/pyynl/cli.py          | 53 +++++------------------------

This breaks tools/net/ynl/tests/ethtool.py which currently imports from cli:

  from cli import schema_dir, spec_dir

Otherwise, changes look good.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-06-30 10:11 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30  0:14 [PATCH net-next 0/2] tools: ynl: pyynl: minor library ease of use improvements Jakub Kicinski
2026-06-30  0:14 ` [PATCH net-next 1/2] tools: ynl: pyynl: re-export the library API from the package root Jakub Kicinski
2026-06-30  7:28   ` Jan Stancek
2026-06-30  8:38   ` Donald Hunter
2026-06-30  0:14 ` [PATCH net-next 2/2] tools: ynl: pyynl: pull the --family resolution logic into the lib Jakub Kicinski
2026-06-30 10:10   ` Donald Hunter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox