From acb9f9376fe8fd131486f909836be7859169dcb6 Mon Sep 17 00:00:00 2001 From: bill Date: Wed, 2 Sep 2026 16:49:52 -0500 Subject: [PATCH 1/2] cleaned up the NSO multi version handling and documentation udpates --- .gitlab-ci.yml.example | 2 +- README.md | 58 +++++++++++++++++++++---------- justfile | 2 +- pkg_mgmt/pyproject.toml | 2 +- pkg_mgmt/src/pkg_mgmt/sync_pkg.py | 3 -- pkg_mgmt/src/pkg_mgmt/toml.py | 31 +++++++---------- pyproject.toml | 8 +++-- 7 files changed, 61 insertions(+), 45 deletions(-) diff --git a/.gitlab-ci.yml.example b/.gitlab-ci.yml.example index 050f1f3..646035f 100644 --- a/.gitlab-ci.yml.example +++ b/.gitlab-ci.yml.example @@ -42,7 +42,7 @@ workflow: .nso-version-matrix: parallel: matrix: - - NSO_VERSION: ["6.5.7", "6.6.3"] + - NSO_VERSION: ["6.6.3", "6.7.3"] .skip-on-release-updates: rules: diff --git a/README.md b/README.md index 83cb182..e5eac5d 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,6 @@ pythonic declarations. ``` [dependency-groups] dev = [ - "ncs_py3", "pkg_mgmt", "ruff>=0.14.13", "pyang>=2.7.1", @@ -246,15 +245,40 @@ pythonic declarations. ``` [dependency-groups] - pyndev-nso = [ + "pyndev-nso-6.6.3" = [ "arista-dcs-cli-5.30==5.30.7+nso6.6.3", ] ``` - The pyndev-nso dependency group is a special placeholder for pyndev NSO - packages that are external to the current workspace. This separate group is - needed for the pyndev specific synchronization process and this array is - manually populated. + The `pyndev-nso-${NSO_VERSION}` dependency group is a special placeholder for + pyndev NSO packages that are external to the current workspace. This + separate group is needed for the pyndev specific synchronization process and + this array is manually populated. + + The pyndev-nso dependency group name is version specific to support multiple + NSO versions in single `uv.lock` file. When more than one version of NSO is + declared there must exist a uv "conflicts" configuration to keep only a + single group installed in the local environment at time. + + pyproject.toml + + ``` + [dependency-groups] + "pyndev-nso-6.6.3" = [ + "arista-dcs-cli-5.30==5.30.7+nso6.6.3", + ] + "pyndev-nso-6.7.3" = [ + "arista-dcs-cli-5.30==5.30.7+nso6.7.3", + ] + + [tool.uv] + conflicts = [ + [ + { group = "pyndev-nso-6.6.3" }, + { group = "pyndev-nso-6.7.3" }, + ], + ] + ``` - NSO_VERSION @@ -262,22 +286,20 @@ pythonic declarations. PEP 440 "local version identifiers". During build all NSO packages are appended with the version that was used for the package build. Internally this is all driven by the top level `.env` variable `NSO_VERSION`. If your - repository only supports one version of NSO this identifier can be ignored - and left off of dependency version matching. If you are concurrently + repository only supports one version of NSO this package identifier can be + ignored and left off of dependency version matching. If you are concurrently developing towards multiple NSO releases the full identifier should be - specified in the version. + specified in the package version. - > As previously mentioned, the pkg\_mgmt tooling does some special handling - > for the NSO project. One of the tricks that impacts the top level - > pyproject.toml is that during a `just sync` the `NSO_VERSION` variable is - > read and any NSO versions are updated to be synchronized with the declared - > project version. Eg. the arista NED above has a local version tag specific - > to set NSO version and a sync function will update that package version - > automatically to ensure consistency. + > The `NSO_VERSION` variable is referenced in all docker commands to select + > a tag for the build and prod versions of the Cisco official containers. + > Additionally, the `just sync` is pinned to interact with dependency-group + > of the form `pyndev-nso-${NSO_VERSION}` so both of these configuration + > items are required. > Anytime the NSO_VERSION is updated locally the project needs to be - > re-initialized with a `just init` and a `just sync` to setup to the proper - > context. + > re-initialized with a `just init` and a `just sync` to install to the + > proper context. This feature can be utilized in CI platforms with tools such as Github's strategy matrix and Gitlabs parallel matrix. This skeleton has a diff --git a/justfile b/justfile index 147720f..f75145f 100644 --- a/justfile +++ b/justfile @@ -46,7 +46,7 @@ sync quiet='': fi just sync-pkg uv sync --reinstall {{ if quiet != '' { '--quiet' } else { '' } }} - uv sync --group pyndev-nso {{ if quiet != '' { '--quiet' } else { '' } }} + uv sync --group "pyndev-nso-${NSO_VERSION}" {{ if quiet != '' { '--quiet' } else { '' } }} # Build local packages, default is '--all-packages', optionally specify one or more package names to build serially build *args: diff --git a/pkg_mgmt/pyproject.toml b/pkg_mgmt/pyproject.toml index ca2ebd4..71b0da0 100644 --- a/pkg_mgmt/pyproject.toml +++ b/pkg_mgmt/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pkg_mgmt" -version = "0.3.0" +version = "0.4.0" description = "pyndev package management helpers" authors = [ { name = "Bill Stephens", email = "bill@es.net" } diff --git a/pkg_mgmt/src/pkg_mgmt/sync_pkg.py b/pkg_mgmt/src/pkg_mgmt/sync_pkg.py index c858c60..cce5f8a 100644 --- a/pkg_mgmt/src/pkg_mgmt/sync_pkg.py +++ b/pkg_mgmt/src/pkg_mgmt/sync_pkg.py @@ -4,19 +4,16 @@ from functools import partial from pathlib import Path -from .toml import sync_pyproject from .utils import sync_managed_files def process_package(pkg: Path, nso_version: str) -> None: """Sync individual package managed files""" - sync_pyproject(nso_version, pkg.name) sync_managed_files(pkg.name) def main() -> None: nso_version = os.getenv("NSO_VERSION", "0.0.0") - sync_pyproject(nso_version) packages = Path("packages") if packages.is_dir(): with ProcessPoolExecutor() as executor: diff --git a/pkg_mgmt/src/pkg_mgmt/toml.py b/pkg_mgmt/src/pkg_mgmt/toml.py index 8e572ee..0a62732 100644 --- a/pkg_mgmt/src/pkg_mgmt/toml.py +++ b/pkg_mgmt/src/pkg_mgmt/toml.py @@ -173,23 +173,6 @@ def update_nso_version(nso_version: str, deps: list[str]) -> None: deps[i] = re.sub(r"\+nso[^.]+\.[^.]+(\.[^.]+)?", f"+nso{nso_version}", dep) -def sync_pyproject(nso_version: str, pkg_name: str | None = None) -> None: - """Synchronize pyproject.toml with .env state""" - config = load_pyproject(pkg_name) - - if "build-system" in config and "requires" in config["build-system"]: - update_nso_version(nso_version, config["build-system"]["requires"]) - - if "project" in config and "dependencies" in config["project"]: - update_nso_version(nso_version, config["project"]["dependencies"]) - - if "dependency-groups" in config: - for group_deps in config["dependency-groups"].values(): - update_nso_version(nso_version, group_deps) - - write_pyproject(config, pkg_name) - - def update_pyproject(pkg_name: str, version: str) -> None: """Update pyndev pyproject.toml with pkg_name""" config = load_pyproject() @@ -249,7 +232,12 @@ def get_nso_dependencies() -> str: """Pull out project NSO package dependencies""" config = load_pyproject() deps = [dep.split("==")[0] for dep in config.get("project", {}).get("dependencies", [])] - pyndev_nso = [dep.split("==")[0] for dep in config.get("dependency-groups", {}).get("pyndev-nso", [])] + pyndev_nso = [ + dep.split("==")[0] + for group, deps in config.get("dependency-groups", {}).items() + if "pyndev-nso" in group + for dep in deps + ] return deps + pyndev_nso @@ -265,7 +253,12 @@ def get_pkg_version(pkg_name: str) -> str: def get_pkg_dependencies(pkg_name: str) -> str: """Pull out package project.dependencies""" config = load_pyproject(pkg_name) - pyndev_nso = [dep.split("==")[0] for dep in config.get("dependency-groups", {}).get("pyndev-nso", [])] + pyndev_nso = [ + dep.split("==")[0] + for group, deps in config.get("dependency-groups", {}).items() + if "pyndev-nso" in group + for dep in deps + ] nso_deps = get_nso_dependencies() return [dep for dep in pyndev_nso if dep in nso_deps] diff --git a/pyproject.toml b/pyproject.toml index adceae9..2d6cabe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pyndev" -dynamic = ["version"] +version = "0.4.0" description = "Python wrapped NSO development environment" readme = "README.md" authors = [ @@ -18,7 +18,11 @@ dev = [ "mdformat>=1.0.0", "mdformat-gfm>=1.0.0", ] -pyndev-nso = [ +"pyndev-nso-6.6.3" = [ +] + +[tool.uv] +conflicts = [ ] [[tool.uv.index]] From 1f145e4cdea0e54f9831b7b269715a942bf18f1f Mon Sep 17 00:00:00 2001 From: bill Date: Fri, 4 Sep 2026 14:59:43 -0500 Subject: [PATCH 2/2] addressed a few issues raised in PR thread --- docs/package-configuration.md | 12 +++++++----- pkg_mgmt/src/pkg_mgmt/toml.py | 14 +++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/package-configuration.md b/docs/package-configuration.md index 1b1dc3f..10c0cb0 100644 --- a/docs/package-configuration.md +++ b/docs/package-configuration.md @@ -133,11 +133,13 @@ package's own `src/` directory. ## Dependencies -Pyndev package pyproject.toml supports two kinds of dependencies. In either -case since uv is managing the project as workspace NSO packages can be -declared much more simply and uv will find the source based on the top level -pyproject.toml configuration for sources and inherit private repository -settings. +Pyndev package pyproject.toml supports two kinds of dependencies. For locking +and consistency per package NSO dependencies should also be declared in the +project top level pyproject.toml. When a top level pyproject.toml defines +versions including the nso version identifiers with or with out conflict groups +the child package pyproject.toml will inherit the state of the workspace. Thus, +it is redundant and not needed to set NSO versions for packages or the special +`pyndev-nso` dependency group name. ### Build Dependencies diff --git a/pkg_mgmt/src/pkg_mgmt/toml.py b/pkg_mgmt/src/pkg_mgmt/toml.py index 0a62732..4f9b854 100644 --- a/pkg_mgmt/src/pkg_mgmt/toml.py +++ b/pkg_mgmt/src/pkg_mgmt/toml.py @@ -228,15 +228,15 @@ def get_pyndev_description(pkg_name: str) -> str: return description -def get_nso_dependencies() -> str: +def get_nso_dependencies() -> list[str]: """Pull out project NSO package dependencies""" config = load_pyproject() deps = [dep.split("==")[0] for dep in config.get("project", {}).get("dependencies", [])] pyndev_nso = [ dep.split("==")[0] - for group, deps in config.get("dependency-groups", {}).items() + for group, group_deps in config.get("dependency-groups", {}).items() if "pyndev-nso" in group - for dep in deps + for dep in group_deps ] return deps + pyndev_nso @@ -250,21 +250,21 @@ def get_pkg_version(pkg_name: str) -> str: return pkg_version -def get_pkg_dependencies(pkg_name: str) -> str: +def get_pkg_dependencies(pkg_name: str) -> list[str]: """Pull out package project.dependencies""" config = load_pyproject(pkg_name) pyndev_nso = [ dep.split("==")[0] - for group, deps in config.get("dependency-groups", {}).items() + for group, group_deps in config.get("dependency-groups", {}).items() if "pyndev-nso" in group - for dep in deps + for dep in group_deps ] nso_deps = get_nso_dependencies() return [dep for dep in pyndev_nso if dep in nso_deps] -def get_pkg_build_dependencies(pkg_name: str) -> str: +def get_pkg_build_dependencies(pkg_name: str) -> list[str]: """Pull out package build-system.requires""" config = load_pyproject(pkg_name) deps = [dep.split("==")[0] for dep in config.get("build-system", {}).get("requires", [])]