From b997dd1a36a6a9f7d51eed8068d949e87d2bd7f8 Mon Sep 17 00:00:00 2001 From: Antonio Balmaseda <68010787+balmasea@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:43:46 +0100 Subject: [PATCH 01/10] Fix/issue 317 (#323) * adding test to cover the failed scenario * fixing test * raising exception when install message has an error * linting * fixing unit test --- pyntc/devices/ios_device.py | 5 ++++- tests/unit/test_devices/test_ios_device.py | 25 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pyntc/devices/ios_device.py b/pyntc/devices/ios_device.py index d2f150fd..a6e8429c 100644 --- a/pyntc/devices/ios_device.py +++ b/pyntc/devices/ios_device.py @@ -718,7 +718,10 @@ def install_os(self, image_name, install_mode=False, read_timeout=2000, **vendor ) # Set a higher read_timeout and send it in try: - self.show(command, read_timeout=read_timeout) + install_message = self.show(command, read_timeout=read_timeout) + if install_message.startswith("FAILED:"): + log.error("Host %s: OS install error for image %s", self.host, image_name) + raise OSInstallError(hostname=self.hostname, desired_boot=image_name) except IOError: log.error("Host %s: IO error for image %s", self.host, image_name) except CommandError: diff --git a/tests/unit/test_devices/test_ios_device.py b/tests/unit/test_devices/test_ios_device.py index a2b53b2d..beb7ea34 100644 --- a/tests/unit/test_devices/test_ios_device.py +++ b/tests/unit/test_devices/test_ios_device.py @@ -389,6 +389,30 @@ def test_install_os_error(self, mock_wait, mock_reboot, mock_set_boot, mock_imag mock_raw_version_data.return_value = DEVICE_FACTS self.assertRaises(ios_module.OSInstallError, self.device.install_os, BOOT_IMAGE) + @mock.patch.object(IOSDevice, "os_version", new_callable=mock.PropertyMock) + @mock.patch.object(IOSDevice, "_image_booted", side_effect=[False, True]) + @mock.patch.object(IOSDevice, "set_boot_options") + @mock.patch.object(IOSDevice, "show") + @mock.patch.object(IOSDevice, "reboot") + @mock.patch.object(IOSDevice, "_wait_for_device_reboot") + @mock.patch.object(IOSDevice, "_raw_version_data") + def test_install_os_not_enough_space( + self, + mock_raw_version_data, + mock_wait, + mock_reboot, + mock_show, + mock_set_boot, + mock_image_booted, + mock_os_version, + ): + mock_raw_version_data.return_value = DEVICE_FACTS + mock_os_version.return_value = "17.4.3" + mock_show.return_value = "FAILED: There is not enough free disk available to perform this operation on switch 1. At least 1276287 KB of free disk is required" + self.assertRaises(ios_module.OSInstallError, self.device.install_os, image_name=BOOT_IMAGE, install_mode=True) + mock_wait.assert_not_called() + mock_reboot.assert_not_called() + if __name__ == "__main__": unittest.main() @@ -1262,6 +1286,7 @@ def test_install_os_install_mode_with_retries( mock_has_reload_happened_recently.side_effect = [False, False, True] mock_image_booted.side_effect = [False, True] mock_sleep.return_value = None + mock_show.return_value = "show must go on" # Call the install os function actual = ios_device.install_os(image_name, install_mode=True) From 92619da4c6be284967c0a5f2083e975a91f04ccd Mon Sep 17 00:00:00 2001 From: itdependsnetworks Date: Thu, 20 Feb 2025 23:15:38 -0500 Subject: [PATCH 02/10] minor package updates to fix docs --- docs/requirements.txt | 9 +- poetry.lock | 582 ++++++++++++++++++++++++++++-------------- pyproject.toml | 9 +- 3 files changed, 394 insertions(+), 206 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index f403e1aa..c4f6af12 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,8 +1,9 @@ -mkdocs==1.3.1 +mkdocs==1.6.0 # Material for MkDocs theme -mkdocs-material==8.3.9 +mkdocs-material==9.5.32 # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs -mkdocstrings==0.19 -mkdocstrings-python==0.7.1 \ No newline at end of file +mkdocstrings==0.25.2 +mkdocstrings-python==1.10.8 +griffe==1.1.1 \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index f78d391b..5fa9566c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "astroid" @@ -29,6 +29,23 @@ files = [ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] + [[package]] name = "bandit" version = "1.7.10" @@ -139,13 +156,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -504,13 +521,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "1.4.0" +version = "1.1.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-1.4.0-py3-none-any.whl", hash = "sha256:e589de8b8c137e99a46ec45f9598fc0ac5b6868ce824b24db09c02d117b89bc5"}, - {file = "griffe-1.4.0.tar.gz", hash = "sha256:8fccc585896d13f1221035d32c50dec65830c87d23f9adb9b1e6f3d63574f7f5"}, + {file = "griffe-1.1.1-py3-none-any.whl", hash = "sha256:0c469411e8d671a545725f5c0851a746da8bd99d354a79fdc4abd45219252efb"}, + {file = "griffe-1.1.1.tar.gz", hash = "sha256:faeb78764c0b2bd010719d6e015d07709b0f260258b5d4dd6c88343d9702aa30"}, ] [package.dependencies] @@ -633,173 +650,174 @@ yamlordereddictloader = "*" [[package]] name = "lxml" -version = "5.3.0" +version = "5.3.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, - {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, - {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, - {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, - {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, - {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, - {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, - {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, - {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, - {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, - {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, - {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, - {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, - {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, - {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, - {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, - {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, - {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, - {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, - {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, - {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, - {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4058f16cee694577f7e4dd410263cd0ef75644b43802a689c2b3c2a7e69453b"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:364de8f57d6eda0c16dcfb999af902da31396949efa0e583e12675d09709881b"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:528f3a0498a8edc69af0559bdcf8a9f5a8bf7c00051a6ef3141fdcf27017bbf5"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4743e30d6f5f92b6d2b7c86b3ad250e0bad8dee4b7ad8a0c44bfb276af89a3"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b5d7f8acf809465086d498d62a981fa6a56d2718135bb0e4aa48c502055f5c"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:928e75a7200a4c09e6efc7482a1337919cc61fe1ba289f297827a5b76d8969c2"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a997b784a639e05b9d4053ef3b20c7e447ea80814a762f25b8ed5a89d261eac"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7b82e67c5feb682dbb559c3e6b78355f234943053af61606af126df2183b9ef9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:f1de541a9893cf8a1b1db9bf0bf670a2decab42e3e82233d36a74eda7822b4c9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:de1fc314c3ad6bc2f6bd5b5a5b9357b8c6896333d27fdbb7049aea8bd5af2d79"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7c0536bd9178f754b277a3e53f90f9c9454a3bd108b1531ffff720e082d824f2"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:68018c4c67d7e89951a91fbd371e2e34cd8cfc71f0bb43b5332db38497025d51"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa826340a609d0c954ba52fd831f0fba2a4165659ab0ee1a15e4aac21f302406"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:796520afa499732191e39fc95b56a3b07f95256f2d22b1c26e217fb69a9db5b5"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3effe081b3135237da6e4c4530ff2a868d3f80be0bda027e118a5971285d42d0"}, + {file = "lxml-5.3.1-cp310-cp310-win32.whl", hash = "sha256:a22f66270bd6d0804b02cd49dae2b33d4341015545d17f8426f2c4e22f557a23"}, + {file = "lxml-5.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:0bcfadea3cdc68e678d2b20cb16a16716887dd00a881e16f7d806c2138b8ff0c"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e220f7b3e8656ab063d2eb0cd536fafef396829cafe04cb314e734f87649058f"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f2cfae0688fd01f7056a17367e3b84f37c545fb447d7282cf2c242b16262607"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67d2f8ad9dcc3a9e826bdc7802ed541a44e124c29b7d95a679eeb58c1c14ade8"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db0c742aad702fd5d0c6611a73f9602f20aec2007c102630c06d7633d9c8f09a"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:198bb4b4dd888e8390afa4f170d4fa28467a7eaf857f1952589f16cfbb67af27"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2a3e412ce1849be34b45922bfef03df32d1410a06d1cdeb793a343c2f1fd666"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8969dbc8d09d9cd2ae06362c3bad27d03f433252601ef658a49bd9f2b22d79"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5be8f5e4044146a69c96077c7e08f0709c13a314aa5315981185c1f00235fe65"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:133f3493253a00db2c870d3740bc458ebb7d937bd0a6a4f9328373e0db305709"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:52d82b0d436edd6a1d22d94a344b9a58abd6c68c357ed44f22d4ba8179b37629"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b6f92e35e2658a5ed51c6634ceb5ddae32053182851d8cad2a5bc102a359b33"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:203b1d3eaebd34277be06a3eb880050f18a4e4d60861efba4fb946e31071a295"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:155e1a5693cf4b55af652f5c0f78ef36596c7f680ff3ec6eb4d7d85367259b2c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22ec2b3c191f43ed21f9545e9df94c37c6b49a5af0a874008ddc9132d49a2d9c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7eda194dd46e40ec745bf76795a7cccb02a6a41f445ad49d3cf66518b0bd9cff"}, + {file = "lxml-5.3.1-cp311-cp311-win32.whl", hash = "sha256:fb7c61d4be18e930f75948705e9718618862e6fc2ed0d7159b2262be73f167a2"}, + {file = "lxml-5.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c809eef167bf4a57af4b03007004896f5c60bd38dc3852fcd97a26eae3d4c9e6"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e69add9b6b7b08c60d7ff0152c7c9a6c45b4a71a919be5abde6f98f1ea16421c"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e52e1b148867b01c05e21837586ee307a01e793b94072d7c7b91d2c2da02ffe"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b382e0e636ed54cd278791d93fe2c4f370772743f02bcbe431a160089025c9"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e49dc23a10a1296b04ca9db200c44d3eb32c8d8ec532e8c1fd24792276522a"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4399b4226c4785575fb20998dc571bc48125dc92c367ce2602d0d70e0c455eb0"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5412500e0dc5481b1ee9cf6b38bb3b473f6e411eb62b83dc9b62699c3b7b79f7"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c93ed3c998ea8472be98fb55aed65b5198740bfceaec07b2eba551e55b7b9ae"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63d57fc94eb0bbb4735e45517afc21ef262991d8758a8f2f05dd6e4174944519"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:b450d7cabcd49aa7ab46a3c6aa3ac7e1593600a1a0605ba536ec0f1b99a04322"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4df0ec814b50275ad6a99bc82a38b59f90e10e47714ac9871e1b223895825468"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b725e70d15906d24615201e650d5b0388b08a5187a55f119f25874d0103f90dd"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a31fa7536ec1fb7155a0cd3a4e3d956c835ad0a43e3610ca32384d01f079ea1c"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c3c8b55c7fc7b7e8877b9366568cc73d68b82da7fe33d8b98527b73857a225f"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d61ec60945d694df806a9aec88e8f29a27293c6e424f8ff91c80416e3c617645"}, + {file = "lxml-5.3.1-cp312-cp312-win32.whl", hash = "sha256:f4eac0584cdc3285ef2e74eee1513a6001681fd9753b259e8159421ed28a72e5"}, + {file = "lxml-5.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:29bfc8d3d88e56ea0a27e7c4897b642706840247f59f4377d81be8f32aa0cfbf"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252"}, + {file = "lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78"}, + {file = "lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332"}, + {file = "lxml-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:016b96c58e9a4528219bb563acf1aaaa8bc5452e7651004894a973f03b84ba81"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82a4bb10b0beef1434fb23a09f001ab5ca87895596b4581fd53f1e5145a8934a"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d68eeef7b4d08a25e51897dac29bcb62aba830e9ac6c4e3297ee7c6a0cf6439"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:f12582b8d3b4c6be1d298c49cb7ae64a3a73efaf4c2ab4e37db182e3545815ac"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2df7ed5edeb6bd5590914cd61df76eb6cce9d590ed04ec7c183cf5509f73530d"}, + {file = "lxml-5.3.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:585c4dc429deebc4307187d2b71ebe914843185ae16a4d582ee030e6cfbb4d8a"}, + {file = "lxml-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:06a20d607a86fccab2fc15a77aa445f2bdef7b49ec0520a842c5c5afd8381576"}, + {file = "lxml-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:057e30d0012439bc54ca427a83d458752ccda725c1c161cc283db07bcad43cf9"}, + {file = "lxml-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4867361c049761a56bd21de507cab2c2a608c55102311d142ade7dab67b34f32"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dddf0fb832486cc1ea71d189cb92eb887826e8deebe128884e15020bb6e3f61"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bcc211542f7af6f2dfb705f5f8b74e865592778e6cafdfd19c792c244ccce19"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaca5a812f050ab55426c32177091130b1e49329b3f002a32934cd0245571307"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:236610b77589faf462337b3305a1be91756c8abc5a45ff7ca8f245a71c5dab70"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:aed57b541b589fa05ac248f4cb1c46cbb432ab82cbd467d1c4f6a2bdc18aecf9"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:75fa3d6946d317ffc7016a6fcc44f42db6d514b7fdb8b4b28cbe058303cb6e53"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:96eef5b9f336f623ffc555ab47a775495e7e8846dde88de5f941e2906453a1ce"}, + {file = "lxml-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:ef45f31aec9be01379fc6c10f1d9c677f032f2bac9383c827d44f620e8a88407"}, + {file = "lxml-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0611da6b07dd3720f492db1b463a4d1175b096b49438761cc9f35f0d9eaaef5"}, + {file = "lxml-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2aca14c235c7a08558fe0a4786a1a05873a01e86b474dfa8f6df49101853a4e"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82fce1d964f065c32c9517309f0c7be588772352d2f40b1574a214bd6e6098"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7aae7a3d63b935babfdc6864b31196afd5145878ddd22f5200729006366bc4d5"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8e0d177b1fe251c3b1b914ab64135475c5273c8cfd2857964b2e3bb0fe196a7"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:6c4dd3bfd0c82400060896717dd261137398edb7e524527438c54a8c34f736bf"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f1208c1c67ec9e151d78aa3435aa9b08a488b53d9cfac9b699f15255a3461ef2"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c6aacf00d05b38a5069826e50ae72751cb5bc27bdc4d5746203988e429b385bb"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5881aaa4bf3a2d086c5f20371d3a5856199a0d8ac72dd8d0dbd7a2ecfc26ab73"}, + {file = "lxml-5.3.1-cp38-cp38-win32.whl", hash = "sha256:45fbb70ccbc8683f2fb58bea89498a7274af1d9ec7995e9f4af5604e028233fc"}, + {file = "lxml-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:7512b4d0fc5339d5abbb14d1843f70499cab90d0b864f790e73f780f041615d7"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5885bc586f1edb48e5d68e7a4b4757b5feb2a496b64f462b4d65950f5af3364f"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b92fe86e04f680b848fff594a908edfa72b31bfc3499ef7433790c11d4c8cd8"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a091026c3bf7519ab1e64655a3f52a59ad4a4e019a6f830c24d6430695b1cf6a"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ffb141361108e864ab5f1813f66e4e1164181227f9b1f105b042729b6c15125"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3715cdf0dd31b836433af9ee9197af10e3df41d273c19bb249230043667a5dfd"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88b72eb7222d918c967202024812c2bfb4048deeb69ca328363fb8e15254c549"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa59974880ab5ad8ef3afaa26f9bda148c5f39e06b11a8ada4660ecc9fb2feb3"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3bb8149840daf2c3f97cebf00e4ed4a65a0baff888bf2605a8d0135ff5cf764e"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:0d6b2fa86becfa81f0a0271ccb9eb127ad45fb597733a77b92e8a35e53414914"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:136bf638d92848a939fd8f0e06fcf92d9f2e4b57969d94faae27c55f3d85c05b"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:89934f9f791566e54c1d92cdc8f8fd0009447a5ecdb1ec6b810d5f8c4955f6be"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8ade0363f776f87f982572c2860cc43c65ace208db49c76df0a21dde4ddd16e"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bfbbab9316330cf81656fed435311386610f78b6c93cc5db4bebbce8dd146675"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:172d65f7c72a35a6879217bcdb4bb11bc88d55fb4879e7569f55616062d387c2"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3c623923967f3e5961d272718655946e5322b8d058e094764180cdee7bab1af"}, + {file = "lxml-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ce0930a963ff593e8bb6fda49a503911accc67dee7e5445eec972668e672a0f0"}, + {file = "lxml-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7b64fcd670bca8800bc10ced36620c6bbb321e7bc1214b9c0c0df269c1dddc2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:afa578b6524ff85fb365f454cf61683771d0170470c48ad9d170c48075f86725"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f5e80adf0aafc7b5454f2c1cb0cde920c9b1f2cbd0485f07cc1d0497c35c5d"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd0b80ac2d8f13ffc906123a6f20b459cb50a99222d0da492360512f3e50f84"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:422c179022ecdedbe58b0e242607198580804253da220e9454ffe848daa1cfd2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:524ccfded8989a6595dbdda80d779fb977dbc9a7bc458864fc9a0c2fc15dc877"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:48fd46bf7155def2e15287c6f2b133a2f78e2d22cdf55647269977b873c65499"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05123fad495a429f123307ac6d8fd6f977b71e9a0b6d9aeeb8f80c017cb17131"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a243132767150a44e6a93cd1dde41010036e1cbc63cc3e9fe1712b277d926ce3"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92ea6d9dd84a750b2bae72ff5e8cf5fdd13e58dda79c33e057862c29a8d5b50"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2f1be45d4c15f237209bbf123a0e05b5d630c8717c42f59f31ea9eae2ad89394"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a83d3adea1e0ee36dac34627f78ddd7f093bb9cfc0a8e97f1572a949b695cb98"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3edbb9c9130bac05d8c3fe150c51c337a471cc7fdb6d2a0a7d3a88e88a829314"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f23cf50eccb3255b6e913188291af0150d89dab44137a69e14e4dcb7be981f1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7e5edac4778127f2bf452e0721a58a1cfa4d1d9eac63bdd650535eb8543615"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:094b28ed8a8a072b9e9e2113a81fda668d2053f2ca9f2d202c2c8c7c2d6516b1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:514fe78fc4b87e7a7601c92492210b20a1b0c6ab20e71e81307d9c2e377c64de"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8fffc08de02071c37865a155e5ea5fce0282e1546fd5bde7f6149fcaa32558ac"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4b0d5cdba1b655d5b18042ac9c9ff50bda33568eb80feaaca4fc237b9c4fbfde"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3031e4c16b59424e8d78522c69b062d301d951dc55ad8685736c3335a97fc270"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb659702a45136c743bc130760c6f137870d4df3a9e14386478b8a0511abcfca"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a11b16a33656ffc43c92a5343a28dc71eefe460bcc2a4923a96f292692709f6"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5ae125276f254b01daa73e2c103363d3e99e3e10505686ac7d9d2442dd4627a"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76722b5ed4a31ba103e0dc77ab869222ec36efe1a614e42e9bcea88a36186fe"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:33e06717c00c788ab4e79bc4726ecc50c54b9bfb55355eae21473c145d83c2d2"}, + {file = "lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml-html-clean"] +html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.11)"] +source = ["Cython (>=3.0.11,<3.1.0)"] [[package]] name = "markdown" -version = "3.3.7" -description = "Python implementation of Markdown." +version = "3.7" +description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, - {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] @@ -930,29 +948,34 @@ files = [ [[package]] name = "mkdocs" -version = "1.3.1" +version = "1.6.0" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, - {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, + {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, + {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, ] [package.dependencies] -click = ">=3.3" +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = ">=4.3" -Jinja2 = ">=2.10.2" -Markdown = ">=3.2.1,<3.4" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" packaging = ">=20.5" -PyYAML = ">=3.10" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -970,24 +993,51 @@ Markdown = ">=3.3" markupsafe = ">=2.0.1" mkdocs = ">=1.1" +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + [[package]] name = "mkdocs-material" -version = "8.3.9" +version = "9.5.32" description = "Documentation that simply works" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-material-8.3.9.tar.gz", hash = "sha256:dc82b667d2a83f0de581b46a6d0949732ab77e7638b87ea35b770b33bc02e75a"}, - {file = "mkdocs_material-8.3.9-py2.py3-none-any.whl", hash = "sha256:263f2721f3abe533b61f7c8bed435a0462620912742c919821ac2d698b4bfe67"}, + {file = "mkdocs_material-9.5.32-py3-none-any.whl", hash = "sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f"}, + {file = "mkdocs_material-9.5.32.tar.gz", hash = "sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120"}, ] [package.dependencies] -jinja2 = ">=3.0.2" -markdown = ">=3.2" -mkdocs = ">=1.3.0" -mkdocs-material-extensions = ">=1.0.3" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] [[package]] name = "mkdocs-material-extensions" @@ -1013,22 +1063,26 @@ files = [ [[package]] name = "mkdocstrings" -version = "0.19.0" +version = "0.25.2" description = "Automatic documentation from sources, for MkDocs." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"}, - {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"}, + {file = "mkdocstrings-0.25.2-py3-none-any.whl", hash = "sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc"}, + {file = "mkdocstrings-0.25.2.tar.gz", hash = "sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc"}, ] [package.dependencies] +click = ">=7.0" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" Markdown = ">=3.3" MarkupSafe = ">=1.1" -mkdocs = ">=1.2" +mkdocs = ">=1.4" mkdocs-autorefs = ">=0.3.1" +platformdirs = ">=2.2.0" pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] @@ -1037,18 +1091,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "0.7.1" +version = "1.10.8" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"}, - {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"}, + {file = "mkdocstrings_python-1.10.8-py3-none-any.whl", hash = "sha256:bb12e76c8b071686617f824029cb1dfe0e9afe89f27fb3ad9a27f95f054dcd89"}, + {file = "mkdocstrings_python-1.10.8.tar.gz", hash = "sha256:5856a59cbebbb8deb133224a540de1ff60bded25e54d8beacc375bb133d39016"}, ] [package.dependencies] -griffe = ">=0.11.1" -mkdocstrings = ">=0.19" +griffe = ">=0.49" +mkdocstrings = ">=0.25" [[package]] name = "mock" @@ -1130,13 +1184,13 @@ textfsm = ">=1.1.3" [[package]] name = "ntc-templates" -version = "7.5.0" +version = "7.7.0" description = "TextFSM Templates for Network Devices, and Python wrapper for TextFSM's CliTable." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "ntc_templates-7.5.0-py3-none-any.whl", hash = "sha256:9d7fb6467ccaaedf8e93e12106e4c46b1610e88d1bcae396b8c2f6a786d9db1c"}, - {file = "ntc_templates-7.5.0.tar.gz", hash = "sha256:b4b1693cd79ef0da5be0c66d58e3c6285d8d264d46832545765c0d394afed0aa"}, + {file = "ntc_templates-7.7.0-py3-none-any.whl", hash = "sha256:314850358394f4edbc3170ce9d3b69f51c2877c67cd9c4c8fe21227cb81f8ab1"}, + {file = "ntc_templates-7.7.0.tar.gz", hash = "sha256:c7b9c2f8306ff6ff1133b5a1614f9627b520bc27c3c514c4e95bf27e221f2a2d"}, ] [package.dependencies] @@ -1153,15 +1207,30 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "paramiko" -version = "3.5.0" +version = "3.5.1" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, - {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, + {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"}, + {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"}, ] [package.dependencies] @@ -1187,15 +1256,18 @@ files = [ [[package]] name = "pbr" -version = "6.1.0" +version = "6.1.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" files = [ - {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, - {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, + {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, + {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, ] +[package.dependencies] +setuptools = "*" + [[package]] name = "platformdirs" version = "4.3.6" @@ -1340,21 +1412,21 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.4" +version = "10.14.3" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.4-py3-none-any.whl", hash = "sha256:cfc28d6a09d19448bcbf8eee3ce098c7d17ff99f7bd3069db4819af181212037"}, - {file = "pymdown_extensions-10.4.tar.gz", hash = "sha256:bc46f11749ecd4d6b71cf62396104b4a200bad3498cb0f5dad1b8502fe461a35"}, + {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, + {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, ] [package.dependencies] -markdown = ">=3.2" +markdown = ">=3.6" pyyaml = "*" [package.extras] -extra = ["pygments (>=2.12)"] +extra = ["pygments (>=2.19.1)"] [[package]] name = "pynacl" @@ -1461,6 +1533,17 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2025.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1537,6 +1620,109 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -1903,4 +2089,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d47b4578c91f6f2f8c6b0dddacaaab6fbf5efc7aa678bd3a6fe133b0d2c1a65e" +content-hash = "0f94165254092368f5a3ceebab113701158c5a95f6c569a5302bd1bf6debcd7e" diff --git a/pyproject.toml b/pyproject.toml index 3f8460fa..63d9a9af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,11 +51,12 @@ toml = "*" # we need to pin flake8 because of package dependencies that cause it to downgrade and # therefore cause issues with linting since older versions do not take .flake8 as config flake8 = "^3.9.2" -mkdocs = "1.3.1" -mkdocs-material = "8.3.9" -mkdocstrings = "0.19" -mkdocstrings-python = "0.7.1" +mkdocs = "1.6.0" +mkdocs-material = "9.5.32" mkdocs-version-annotations = "1.0.0" +mkdocstrings = "0.25.2" +mkdocstrings-python = "1.10.8" +griffe = "1.1.1" [tool.pyntc] string_required = "some string" From ba17b84a68d4e971311ead2ba1c886b1ce5bfc17 Mon Sep 17 00:00:00 2001 From: Josh VanDeraa Date: Mon, 24 Feb 2025 20:58:29 -0600 Subject: [PATCH 03/10] Update to Ubuntu 24.04 (#325) --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 333ac43d..211cf64f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ env: jobs: black: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: INVOKE_LOCAL: "True" steps: @@ -28,7 +28,7 @@ jobs: - name: "Linting: black" run: "poetry run invoke black" bandit: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: INVOKE_LOCAL: "True" steps: @@ -41,7 +41,7 @@ jobs: needs: - "black" pydocstyle: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: INVOKE_LOCAL: "True" steps: @@ -54,7 +54,7 @@ jobs: needs: - "black" flake8: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: INVOKE_LOCAL: "True" steps: @@ -67,7 +67,7 @@ jobs: needs: - "black" yamllint: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: INVOKE_LOCAL: "True" steps: @@ -84,7 +84,7 @@ jobs: fail-fast: true matrix: python-version: ["3.8", "3.11"] - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: PYTHON_VER: "${{ matrix.python-version }}" steps: @@ -115,7 +115,7 @@ jobs: - "flake8" - "yamllint" pylint: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" strategy: fail-fast: true matrix: @@ -156,7 +156,7 @@ jobs: fail-fast: true matrix: python-version: ["3.8", "3.11"] - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" env: PYTHON_VER: "${{ matrix.python-version }}" steps: @@ -194,7 +194,7 @@ jobs: - "yamllint" publish_gh: name: "Publish to GitHub" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" @@ -223,7 +223,7 @@ jobs: - "pytest" publish_pypi: name: "Push Package to PyPI" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-24.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" From c3439043d3f9fdeba341138a47419fe6b42551f1 Mon Sep 17 00:00:00 2001 From: Gary McCann Date: Tue, 15 Jul 2025 19:17:06 +0100 Subject: [PATCH 04/10] Housekeeping: Fix CI Pipeline - updating GHA to supported versions (#332) * Housekeeping: Fix CI Pipeline - updating GHA to supported versions * fixing py3.8 to py3.9+ * poetry lock file added * fix pylint disables * fix pylint * fix yamllint * fix pylint arguments * fixed to set lower as py3.9 * adding lock file --- .github/workflows/ci.yml | 64 ++-- poetry.lock | 650 ++++++++++++++++++--------------- pyntc/devices/aireos_device.py | 8 +- pyntc/devices/asa_device.py | 5 +- pyntc/devices/eos_device.py | 1 + pyntc/devices/f5_device.py | 2 +- pyntc/devices/ios_device.py | 1 + pyntc/devices/nxos_device.py | 4 +- pyproject.toml | 42 +-- tasks.py | 14 +- 10 files changed, 429 insertions(+), 362 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 211cf64f..5f4dd83e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,9 @@ jobs: INVOKE_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Linting: black" run: "poetry run invoke black" bandit: @@ -33,9 +33,9 @@ jobs: INVOKE_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Linting: bandit" run: "poetry run invoke bandit" needs: @@ -46,9 +46,9 @@ jobs: INVOKE_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Linting: pydocstyle" run: "poetry run invoke pydocstyle" needs: @@ -59,9 +59,9 @@ jobs: INVOKE_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Linting: flake8" run: "poetry run invoke flake8" needs: @@ -72,9 +72,9 @@ jobs: INVOKE_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Linting: yamllint" run: "poetry run invoke yamllint" needs: @@ -83,22 +83,22 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] runs-on: "ubuntu-24.04" env: PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Get image version" run: "echo IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Build" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v6" with: builder: "${{ steps.buildx.outputs.name }}" context: "./" @@ -119,21 +119,21 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8"] + python-version: ["3.9", "3.10", "3.11", "3.12"] env: PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Get image version" run: "echo IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Load the image from cache" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v6" with: builder: "${{ steps.buildx.outputs.name }}" context: "./" @@ -155,22 +155,22 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] runs-on: "ubuntu-24.04" env: PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Get image version" run: "echo IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Load the image from cache" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v6" with: builder: "${{ steps.buildx.outputs.name }}" context: "./" @@ -195,12 +195,13 @@ jobs: publish_gh: name: "Publish to GitHub" runs-on: "ubuntu-24.04" - if: "startsWith(github.ref, 'refs/tags/v')" + # yamllint disable-line rule:quoted-strings + if: startsWith(github.ref, 'refs/tags/v') steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: python-version: "3.9" - name: "Install Python Packages" @@ -224,12 +225,13 @@ jobs: publish_pypi: name: "Push Package to PyPI" runs-on: "ubuntu-24.04" - if: "startsWith(github.ref, 'refs/tags/v')" + # yamllint disable-line rule:quoted-strings + if: startsWith(github.ref, 'refs/tags/v') steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: python-version: "3.9" - name: "Install Python Packages" diff --git a/poetry.lock b/poetry.lock index 5fa9566c..75db2393 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,34 +1,19 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "astroid" -version = "3.2.4" +version = "3.3.9" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" files = [ - {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, - {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, + {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, + {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, ] [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -[[package]] -name = "astunparse" -version = "1.6.3" -description = "An AST unparser for Python" -optional = false -python-versions = "*" -files = [ - {file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, - {file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, -] - -[package.dependencies] -six = ">=1.6.1,<2.0" -wheel = ">=0.23.0,<1.0" - [[package]] name = "babel" version = "2.17.0" @@ -40,21 +25,18 @@ files = [ {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - [package.extras] dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "bandit" -version = "1.7.10" +version = "1.8.3" description = "Security oriented static analyser for python code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"}, - {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"}, + {file = "bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8"}, + {file = "bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a"}, ] [package.dependencies] @@ -72,36 +54,62 @@ yaml = ["PyYAML"] [[package]] name = "bcrypt" -version = "4.2.1" +version = "4.3.0" description = "Modern password hashing for your software and your servers" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dbd0747208912b1e4ce730c6725cb56c07ac734b3629b60d4398f082ea718ad"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:aaa2e285be097050dba798d537b6efd9b698aa88eef52ec98d23dcd6d7cf6fea"}, - {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:76d3e352b32f4eeb34703370e370997065d28a561e4a18afe4fef07249cb4396"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7703ede632dc945ed1172d6f24e9f30f27b1b1a067f32f68bf169c5f08d0425"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89df2aea2c43be1e1fa066df5f86c8ce822ab70a30e4c210968669565c0f4685"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:04e56e3fe8308a88b77e0afd20bec516f74aecf391cdd6e374f15cbed32783d6"}, - {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cfdf3d7530c790432046c40cda41dfee8c83e29482e6a604f8930b9930e94139"}, - {file = "bcrypt-4.2.1-cp37-abi3-win32.whl", hash = "sha256:adadd36274510a01f33e6dc08f5824b97c9580583bd4487c564fc4617b328005"}, - {file = "bcrypt-4.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:8c458cd103e6c5d1d85cf600e546a639f234964d0228909d8f8dbeebff82d526"}, - {file = "bcrypt-4.2.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8ad2f4528cbf0febe80e5a3a57d7a74e6635e41af1ea5675282a33d769fba413"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909faa1027900f2252a9ca5dfebd25fc0ef1417943824783d1c8418dd7d6df4a"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cde78d385d5e93ece5479a0a87f73cd6fa26b171c786a884f955e165032b262c"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:533e7f3bcf2f07caee7ad98124fab7499cb3333ba2274f7a36cf1daee7409d99"}, - {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:687cf30e6681eeda39548a93ce9bfbb300e48b4d445a43db4298d2474d2a1e54"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:041fa0155c9004eb98a232d54da05c0b41d4b8e66b6fc3cb71b4b3f6144ba837"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f85b1ffa09240c89aa2e1ae9f3b1c687104f7b2b9d2098da4e923f1b7082d331"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c6f5fa3775966cca251848d4d5393ab016b3afed251163c1436fefdec3b02c84"}, - {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:807261df60a8b1ccd13e6599c779014a362ae4e795f5c59747f60208daddd96d"}, - {file = "bcrypt-4.2.1-cp39-abi3-win32.whl", hash = "sha256:b588af02b89d9fad33e5f98f7838bf590d6d692df7153647724a7f20c186f6bf"}, - {file = "bcrypt-4.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:e84e0e6f8e40a242b11bce56c313edc2be121cec3e0ec2d76fce01f6af33c07c"}, - {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76132c176a6d9953cdc83c296aeaed65e1a708485fd55abf163e0d9f8f16ce0e"}, - {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e158009a54c4c8bc91d5e0da80920d048f918c61a581f0a63e4e93bb556d362f"}, - {file = "bcrypt-4.2.1.tar.gz", hash = "sha256:6765386e3ab87f569b276988742039baab087b2cdb01e809d74e74503c2faafe"}, + {file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd"}, + {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af"}, + {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231"}, + {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c"}, + {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f"}, + {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d"}, + {file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4"}, + {file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669"}, + {file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb"}, + {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d"}, + {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f"}, + {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732"}, + {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef"}, + {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304"}, + {file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51"}, + {file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62"}, + {file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe"}, + {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0"}, + {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f"}, + {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23"}, + {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe"}, + {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505"}, + {file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a"}, + {file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b"}, + {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1"}, + {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d"}, + {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492"}, + {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90"}, + {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a"}, + {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce"}, + {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8"}, + {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938"}, + {file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18"}, ] [package.extras] @@ -110,33 +118,33 @@ typecheck = ["mypy"] [[package]] name = "black" -version = "24.8.0" +version = "25.1.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +python-versions = ">=3.9" +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [package.dependencies] @@ -150,7 +158,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -531,7 +539,6 @@ files = [ ] [package.dependencies] -astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""} colorama = ">=0.4" [[package]] @@ -550,13 +557,13 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.5.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] @@ -568,18 +575,18 @@ cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -595,27 +602,28 @@ files = [ [[package]] name = "isort" -version = "5.13.2" +version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, ] [package.extras] -colors = ["colorama (>=0.4.6)"] +colors = ["colorama"] +plugins = ["setuptools"] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -626,13 +634,13 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "junos-eznc" -version = "2.7.2" +version = "2.7.3" description = "Junos 'EZ' automation for non-programmers" optional = false python-versions = ">=3.8" files = [ - {file = "junos-eznc-2.7.2.tar.gz", hash = "sha256:7c7e6f8e9bb9d0d034ffaeb592e400dd114f03db44c3bb608c951e88483c825d"}, - {file = "junos_eznc-2.7.2-py2.py3-none-any.whl", hash = "sha256:72ace7fe635efbb0480126b54f8ce79e9fb519f2c4aa53233318f1b2cb32b4b0"}, + {file = "junos-eznc-2.7.3.tar.gz", hash = "sha256:15a9aff9da4bf25dc3c7e54fdf98f3eff5dc4fe8edb79075f54039bcf7a71bd1"}, + {file = "junos_eznc-2.7.3-py2.py3-none-any.whl", hash = "sha256:d3316bfa197900113b4faedd581999f4c8c596ed59277f7956ed2c0bf1d2cbe4"}, ] [package.dependencies] @@ -846,71 +854,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -979,13 +988,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp [[package]] name = "mkdocs-autorefs" -version = "1.2.0" +version = "1.4.1" description = "Automatically link across pages in MkDocs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, - {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, + {file = "mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f"}, + {file = "mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079"}, ] [package.dependencies] @@ -1106,13 +1115,13 @@ mkdocstrings = ">=0.25" [[package]] name = "mock" -version = "5.1.0" +version = "5.2.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" files = [ - {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, - {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, + {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, + {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, ] [package.extras] @@ -1163,34 +1172,34 @@ nicer-shell = ["ipython"] [[package]] name = "netmiko" -version = "4.4.0" +version = "4.5.0" description = "Multi-vendor library to simplify legacy CLI connections to network devices" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "netmiko-4.4.0-py3-none-any.whl", hash = "sha256:2ff4683f013fac0f80715286c7d3250e89166aefc4421cb75d3ff483f2ebbbc0"}, - {file = "netmiko-4.4.0.tar.gz", hash = "sha256:25ff1237976aa3ff2cacf04949314638c899220a1675bd029e31b07ce20ce3b6"}, + {file = "netmiko-4.5.0-py3-none-any.whl", hash = "sha256:42d81fb906339af1fb5c6899f181f5cb5c9b03311262ba56c696b113e1f2f61a"}, + {file = "netmiko-4.5.0.tar.gz", hash = "sha256:dbdfc20b6caaf8e5d7a570bb1b42a26b9a6f8d8234e91f5c65f4dbfe0c0e4f50"}, ] [package.dependencies] -cffi = ">=1.17.0rc1" ntc-templates = ">=3.1.0" paramiko = ">=2.9.5" pyserial = ">=3.3" -pyyaml = ">=5.3" +pyyaml = ">=6.0.2" +rich = ">=13.8" +"ruamel.yaml" = ">=0.17" scp = ">=0.13.6" -setuptools = ">=65.0.0" textfsm = ">=1.1.3" [[package]] name = "ntc-templates" -version = "7.7.0" +version = "7.8.0" description = "TextFSM Templates for Network Devices, and Python wrapper for TextFSM's CliTable." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "ntc_templates-7.7.0-py3-none-any.whl", hash = "sha256:314850358394f4edbc3170ce9d3b69f51c2877c67cd9c4c8fe21227cb81f8ab1"}, - {file = "ntc_templates-7.7.0.tar.gz", hash = "sha256:c7b9c2f8306ff6ff1133b5a1614f9627b520bc27c3c514c4e95bf27e221f2a2d"}, + {file = "ntc_templates-7.8.0-py3-none-any.whl", hash = "sha256:db0e8328117b6ce51e0fffee291a2cf7d8771977fbf4d751e457c49748e9c56b"}, + {file = "ntc_templates-7.8.0.tar.gz", hash = "sha256:693536d228de466e0ce895226586f090eb91fcd4e209aeb12527391b4340fc62"}, ] [package.dependencies] @@ -1270,19 +1279,19 @@ setuptools = "*" [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -1382,29 +1391,29 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "3.2.7" +version = "3.3.6" description = "python code static checker" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" files = [ - {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, - {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, + {file = "pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6"}, + {file = "pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a"}, ] [package.dependencies] -astroid = ">=3.2.4,<=3.3.0-dev0" +astroid = ">=3.3.8,<=3.4.0.dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +isort = ">=4.2.5,<5.13 || >5.13,<7" mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +platformdirs = ">=2.2" +tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] @@ -1471,13 +1480,13 @@ scp = "*" [[package]] name = "pyparsing" -version = "3.1.4" +version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, ] [package.extras] @@ -1499,13 +1508,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "8.3.4" +version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, ] [package.dependencies] @@ -1533,17 +1542,6 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "pytz" -version = "2025.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, - {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1763,13 +1761,13 @@ fixture = ["fixtures"] [[package]] name = "rich" -version = "13.9.4" +version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, ] [package.dependencies] @@ -1780,6 +1778,79 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruamel-yaml" +version = "0.18.10" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, + {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, + {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, +] + [[package]] name = "scp" version = "0.14.5" @@ -1796,23 +1867,23 @@ paramiko = "*" [[package]] name = "setuptools" -version = "75.3.0" +version = "78.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, - {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, + {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, + {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" @@ -1838,13 +1909,13 @@ files = [ [[package]] name = "stevedore" -version = "5.3.0" +version = "5.4.1" description = "Manage dynamic plugins for Python applications" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, - {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, ] [package.dependencies] @@ -1948,24 +2019,24 @@ test = ["pytest"] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, + {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, ] [[package]] name = "urllib3" -version = "2.2.3" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -1976,74 +2047,55 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "watchdog" -version = "4.0.2" +version = "6.0.0" description = "Filesystem events monitoring" optional = false -python-versions = ">=3.8" -files = [ - {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, - {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, - {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, - {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, - {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, - {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, - {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, - {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, - {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, - {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, - {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, +python-versions = ">=3.9" +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] watchmedo = ["PyYAML (>=3.10)"] -[[package]] -name = "wheel" -version = "0.45.1" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, - {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, -] - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] - [[package]] name = "yamllint" -version = "1.35.1" +version = "1.37.0" description = "A linter for YAML files." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "yamllint-1.35.1-py3-none-any.whl", hash = "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3"}, - {file = "yamllint-1.35.1.tar.gz", hash = "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd"}, + {file = "yamllint-1.37.0-py3-none-any.whl", hash = "sha256:c03ab4e79ab4af964c8eb16ac9746880fc76a3bb0ffb14925b9a55220ae7dda0"}, + {file = "yamllint-1.37.0.tar.gz", hash = "sha256:ead81921d4d87216b2528b7a055664708f9fb8267beb0c427cb706ac6ab93580"}, ] [package.dependencies] @@ -2069,13 +2121,13 @@ pyyaml = "*" [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -2088,5 +2140,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "0f94165254092368f5a3ceebab113701158c5a95f6c569a5302bd1bf6debcd7e" +python-versions = "^3.9" +content-hash = "171e885da45f735a023712ee2c752eb178aca05c5b3f7c9ed4a004798c215198" diff --git a/pyntc/devices/aireos_device.py b/pyntc/devices/aireos_device.py index 129e1695..2de31cf6 100644 --- a/pyntc/devices/aireos_device.py +++ b/pyntc/devices/aireos_device.py @@ -69,7 +69,8 @@ class AIREOSDevice(BaseDevice): vendor = "cisco" active_redundancy_states = {None, "active"} - def __init__( # nosec # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-positional-arguments + def __init__( # nosec self, host, username, password, secret="", port=None, confirm_active=True, **kwargs ): # noqa: D403 """ @@ -423,7 +424,7 @@ def ap_boot_options(self): } for ap in ap_boot_options } - log.debug("Host %s: Boot options: {boot_options_by_ap}", self.host, boot_options_by_ap) + log.debug("Host %s: Boot options: %s", self.host, boot_options_by_ap) return boot_options_by_ap @property @@ -852,6 +853,7 @@ def facts(self): """ raise NotImplementedError + # pylint: disable=too-many-arguments, too-many-positional-arguments def file_copy( self, username, @@ -1199,7 +1201,7 @@ def redundancy_mode(self): """ high_availability = self.show("show redundancy summary") ha_mode = re.search(r"^\s*Redundancy\s+Mode\s*=\s*(.+?)\s*$", high_availability, re.M) - log.debug("Host %s: Redundancy mode: {ha_mode.group(1).lower()}", self.host, ha_mode.group(1).lower()) + log.debug("Host %s: Redundancy mode: %s", self.host, ha_mode.group(1).lower()) return ha_mode.group(1).lower() @property diff --git a/pyntc/devices/asa_device.py b/pyntc/devices/asa_device.py index 398a000b..f5cf89d8 100644 --- a/pyntc/devices/asa_device.py +++ b/pyntc/devices/asa_device.py @@ -38,6 +38,7 @@ class ASADevice(BaseDevice): vendor = "cisco" active_redundancy_states = {None, "active"} + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__(self, host: str, username: str, password: str, secret="", port=None, **kwargs): # nosec """ Pyntc Device constructor for Cisco ASA. @@ -162,7 +163,7 @@ def _get_ipv4_addresses(self, host: str) -> Dict[str, List[IPv4Address]]: results = { interface: [IPv4Interface(f"{address}/{netmask}")] for interface, address, netmask in re_ip_addresses } - log.debug("Host %s: ip interfaces %s", self.host) + log.debug("Host %s: ip interfaces %s", self.host, results) return results def _get_ipv6_addresses(self, host: str) -> Dict[str, List[IPv6Address]]: @@ -693,7 +694,7 @@ def ip_protocol(self) -> str: """ protocol = f"ipv{self.ip_address.version}" - log.debug("Host %s: IP protocol for paramiko is %s.", self.host) + log.debug("Host %s: IP protocol for paramiko is %s.", self.host, protocol) return protocol def is_active(self): diff --git a/pyntc/devices/eos_device.py b/pyntc/devices/eos_device.py index 7ee87a35..8d47e860 100644 --- a/pyntc/devices/eos_device.py +++ b/pyntc/devices/eos_device.py @@ -41,6 +41,7 @@ class EOSDevice(BaseDevice): vendor = "arista" + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__(self, host, username, password, transport="http", port=None, timeout=None, **kwargs): # noqa: D403 """PyNTC Device implementation for Arista EOS. diff --git a/pyntc/devices/f5_device.py b/pyntc/devices/f5_device.py index 06da359c..a383fd23 100644 --- a/pyntc/devices/f5_device.py +++ b/pyntc/devices/f5_device.py @@ -607,7 +607,7 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): if not self._image_match(image_name=file_basename, checksum=local_md5sum): log.debug("Host %s: File %s does not already exist on remote.", self.host, src) return False - log.debug("Host %s: File %s already exists on remote.", self.host) + log.debug("Host %s: File %s already exists on remote.", self.host, src) return True def image_installed(self, image_name, volume): diff --git a/pyntc/devices/ios_device.py b/pyntc/devices/ios_device.py index a6e8429c..9b6d6810 100644 --- a/pyntc/devices/ios_device.py +++ b/pyntc/devices/ios_device.py @@ -43,6 +43,7 @@ class IOSDevice(BaseDevice): vendor = "cisco" active_redundancy_states = {None, "active"} + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( # nosec self, host, username, password, secret="", port=None, confirm_active=True, **kwargs ): # noqa: D403 diff --git a/pyntc/devices/nxos_device.py b/pyntc/devices/nxos_device.py index 0e1aefa9..48f26be9 100644 --- a/pyntc/devices/nxos_device.py +++ b/pyntc/devices/nxos_device.py @@ -28,6 +28,7 @@ class NXOSDevice(BaseDevice): vendor = "cisco" + # pylint: disable=too-many-arguments, too-many-positional-arguments def __init__( self, host, username, password, transport="http", timeout=30, port=None, verify=True, **kwargs ): # noqa: D403 @@ -350,7 +351,8 @@ def reboot(self, wait_for_reload=False, **kwargs): >> """ if kwargs.get("confirm"): - log.warning("Passing 'confirm' to reboot method is deprecated.", DeprecationWarning) + log.warning("Passing 'confirm' to reboot method is deprecated.") + raise DeprecationWarning("Passing 'confirm' to reboot method is deprecated.") try: self.native.show_list(["terminal dont-ask", "reload"]) # The native reboot is not always properly disabling confirmation. Above is more consistent. diff --git a/pyproject.toml b/pyproject.toml index 63d9a9af..446c9e5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,10 @@ classifiers = [ "Intended Audience :: Developers", "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", - # "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] include = [ "LICENSE", @@ -24,7 +24,7 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" f5-sdk = "^3.0.21" junos-eznc = "^2.6" netmiko = "^4.0" @@ -69,7 +69,7 @@ file = "README.md" [tool.black] line-length = 120 -target-version = ['py37'] +target-version = ['py39'] include = '\.pyi?$' exclude = ''' ( @@ -97,24 +97,24 @@ ignore=[".venv", "tests"] # No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. no-docstring-rgx="^(_|test_|Meta$)" -[tool.pylint.messages_control] +[tool.pylint."messages control"] # Line length is enforced by Black, so pylint doesn't need to check it. # Pylint and Black disagree about how to format multi-line arrays; Black wins. -disable = """, - abstract-method, - arguments-differ, - arguments-renamed, - attribute-defined-outside-init, - consider-iterating-dictionary, - duplicate-code, - inconsistent-return-statements, - line-too-long, - raise-missing-from, - too-many-arguments, - too-many-instance-attributes, - too-many-lines, - too-many-public-methods, - """ +disable = [ + "abstract-method", + "arguments-differ", + "arguments-renamed", + "attribute-defined-outside-init", + "consider-iterating-dictionary", + "duplicate-code", + "inconsistent-return-statements", + "line-too-long", + "raise-missing-from", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-lines", + "too-many-public-methods" + ] [tool.pylint.miscellaneous] # Don't flag TODO as a failure, let us commit with things that still need to be done in the code diff --git a/tasks.py b/tasks.py index 3e7264d7..effc231a 100644 --- a/tasks.py +++ b/tasks.py @@ -2,7 +2,6 @@ import os import sys -from distutils.util import strtobool from invoke import task @@ -13,7 +12,8 @@ def is_truthy(arg): - """Convert "truthy" strings into Booleans. + """ + Convert "truthy" strings into Booleans. Examples: >>> is_truthy('yes') @@ -25,14 +25,20 @@ def is_truthy(arg): """ if isinstance(arg, bool): return arg - return bool(strtobool(arg)) + + val = str(arg).lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + if val in ("n", "no", "f", "false", "off", "0"): + return False + raise ValueError(f"Invalid truthy value: `{arg}`") PYPROJECT_CONFIG = toml.load("pyproject.toml") TOOL_CONFIG = PYPROJECT_CONFIG["tool"]["poetry"] # Can be set to a separate Python version to be used for launching or building image -PYTHON_VER = os.getenv("PYTHON_VER", "3.8") +PYTHON_VER = os.getenv("PYTHON_VER", "3.11") # Name of the docker image/image IMAGE_NAME = os.getenv("IMAGE_NAME", TOOL_CONFIG["name"]) # Tag for the image From 74fda9b9589b2d4bd253c12c13b9d5143802fd72 Mon Sep 17 00:00:00 2001 From: Jeff Kala <48843785+jeffkala@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:55:54 -0600 Subject: [PATCH 05/10] Initial pass at updating to 2025 best practices (#336) * Initial pass at updated to 2025 best practices * fix ci missing build stage * fix ci invoke lock issue * fix ci remove markdownlint * fix ci comment out doc builds for time being * update Dockerfile to use poetry install script * fix ruff target-version * Apply suggestions from code review Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com> * fix print debug * Update tasks.py Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com> * remove unused import of os * fix ruff target-version * fix formatting * fixing tagging * fixing env vars in ci * fixing env vars in ci * fix dockerfile properly this time * fix dockerfile properly this time * make poetry install match nautobot best practices --------- Co-authored-by: Joe Wesch <10467633+joewesch@users.noreply.github.com> --- .bandit.yml | 6 - .flake8 | 6 - .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/styles/dummy.txt | 5 - .github/workflows/ci.yml | 190 ++-- .readthedocs.yml | 2 +- .vale.ini | 20 - .yamllint.yml | 5 +- Dockerfile | 42 +- changes/335.housekeeping | 3 + example.invoke.yml | 7 + poetry.lock | 936 ++++++++---------- pyntc/__init__.py | 6 +- pyntc/devices/__init__.py | 11 +- pyntc/devices/aireos_device.py | 40 +- pyntc/devices/asa_device.py | 8 +- pyntc/devices/base_device.py | 9 +- pyntc/devices/eos_device.py | 7 +- pyntc/devices/f5_device.py | 13 +- pyntc/devices/ios_device.py | 8 +- pyntc/devices/iosxewlc_device.py | 3 + pyntc/devices/jnpr_device.py | 9 +- pyntc/devices/nxos_device.py | 12 +- pyntc/devices/system_features/__init__.py | 1 + .../devices/system_features/vlans/__init__.py | 1 + pyntc/devices/tables/__init__.py | 1 + pyntc/devices/tables/jnpr/__init__.py | 1 + pyntc/log.py | 4 +- pyntc/utils/__init__.py | 3 +- pyntc/utils/converters.py | 8 +- pyntc/utils/templates/__init__.py | 2 +- pyproject.toml | 169 ++-- tasks.py | 233 +++-- tests/unit/conftest.py | 1 + .../test_devices/device_mocks/eos/__init__.py | 5 +- .../device_mocks/nxos/__init__.py | 3 +- tests/unit/test_devices/test_aireos_device.py | 12 +- tests/unit/test_devices/test_asa_device.py | 2 +- tests/unit/test_devices/test_ios_device.py | 2 +- tests/unit/test_errors.py | 11 +- tests/unit/test_infra.py | 6 +- .../test_vlan/mocks/eos/__init__.py | 3 +- .../test_vlan/test_eos_vlan.py | 8 +- 44 files changed, 920 insertions(+), 910 deletions(-) delete mode 100644 .bandit.yml delete mode 100644 .flake8 delete mode 100644 .github/styles/dummy.txt delete mode 100644 .vale.ini create mode 100644 changes/335.housekeeping create mode 100644 example.invoke.yml diff --git a/.bandit.yml b/.bandit.yml deleted file mode 100644 index 56f7a83b..00000000 --- a/.bandit.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -skips: [] -# No need to check for security issues in the test scripts! -exclude_dirs: - - "./tests/" - - "./.venv/" diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 1587fc6c..00000000 --- a/.flake8 +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -# E501: Line length is enforced by Black, so flake8 doesn't need to check it -# W503: Black disagrees with this rule, as does PEP 8; Black wins -ignore = E501, W503 -exclude = - .venv diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1a03085d..6f921a6c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,8 +4,8 @@ about: Report a reproducible bug in the current release of pyntc --- ### Environment -* Python version: -* pyntc version: +* Python version: +* pyntc version: ### Expected Behavior diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cf992e47..f0c71b9e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,7 +5,7 @@ about: Propose a new feature or enhancement --- ### Environment -* pyntc version: +* pyntc version: " +issue_format = "[#{issue}](https://github.com/networktocode/pyntc/issues/{issue})" + +[[tool.towncrier.type]] +directory = "security" +name = "Security" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "dependencies" +name = "Dependencies" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true + +[[tool.towncrier.type]] +directory = "housekeeping" +name = "Housekeeping" +showcontent = true \ No newline at end of file diff --git a/tasks.py b/tasks.py index effc231a..515dda44 100644 --- a/tasks.py +++ b/tasks.py @@ -1,24 +1,16 @@ """Tasks for use with Invoke.""" -import os -import sys - -from invoke import task - -try: - import toml -except ImportError: - sys.exit("Please make sure to `pip install toml` or enable the Poetry shell and run `poetry install`.") +from invoke import Collection, Exit +from invoke import task as invoke_task def is_truthy(arg): - """ - Convert "truthy" strings into Booleans. + """Convert "truthy" strings into Booleans. - Examples: + Examples + -------- >>> is_truthy('yes') True - Args: arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. Raises ValueError if val is anything else. @@ -34,45 +26,68 @@ def is_truthy(arg): raise ValueError(f"Invalid truthy value: `{arg}`") -PYPROJECT_CONFIG = toml.load("pyproject.toml") -TOOL_CONFIG = PYPROJECT_CONFIG["tool"]["poetry"] +# Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html +# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_PYNTC_xxx +namespace = Collection("pyntc") +namespace.configure( + { + "pyntc": { + "project_name": "pyntc", + "python_ver": "3.12", + "local": False, + "image_name": "pyntc", + "image_ver": "latest", + "pwd": ".", + } + } +) + + +# pylint: disable=keyword-arg-before-vararg +def task(function=None, *args, **kwargs): + """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" + + def task_wrapper(function=None): + """Wrapper around invoke.task to add the task to the namespace as well.""" + if args or kwargs: + task_func = invoke_task(*args, **kwargs)(function) + else: + task_func = invoke_task(function) + namespace.add_task(task_func) + return task_func -# Can be set to a separate Python version to be used for launching or building image -PYTHON_VER = os.getenv("PYTHON_VER", "3.11") -# Name of the docker image/image -IMAGE_NAME = os.getenv("IMAGE_NAME", TOOL_CONFIG["name"]) -# Tag for the image -IMAGE_VER = os.getenv("IMAGE_VER", f"{TOOL_CONFIG['version']}-py{PYTHON_VER}") -# Gather current working directory for Docker commands -PWD = os.getcwd() -# Local or Docker execution provide "local" to run locally without docker execution -INVOKE_LOCAL = is_truthy(os.getenv("INVOKE_LOCAL", False)) # pylint: disable=W1508 + if function: + # The decorator was called with no arguments + return task_wrapper(function) + # The decorator was called with arguments + return task_wrapper -def run_cmd(context, exec_cmd, local=INVOKE_LOCAL, port=None): +def run_command(context, exec_cmd, port=None): """Wrapper to run the invoke task commands. Args: context ([invoke.task]): Invoke task object. exec_cmd ([str]): Command to run. - local (bool): Define as `True` to execute locally port (int): Used to serve local docs. Returns: result (obj): Contains Invoke result from running task. """ - if is_truthy(local): + if is_truthy(context.pyntc.local): print(f"LOCAL - Running command {exec_cmd}") result = context.run(exec_cmd, pty=True) else: - print(f"DOCKER - Running command: {exec_cmd} container: {IMAGE_NAME}:{IMAGE_VER}") + print(f"DOCKER - Running command: {exec_cmd} container: {context.pyntc.image_name}:{context.pyntc.image_ver}") if port: result = context.run( - f"docker run -it -p {port} -v {PWD}:/local {IMAGE_NAME}:{IMAGE_VER} sh -c '{exec_cmd}'", pty=True + f"docker run -it -p {port} -v {context.pyntc.pwd}:/local {context.pyntc.image_name}:{context.pyntc.image_ver} sh -c '{exec_cmd}'", + pty=True, ) else: result = context.run( - f"docker run -it -v {PWD}:/local {IMAGE_NAME}:{IMAGE_VER} sh -c '{exec_cmd}'", pty=True + f"docker run -it -v {context.pyntc.pwd}:/local {context.pyntc.image_name}:{context.pyntc.image_ver} sh -c '{exec_cmd}'", + pty=True, ) return result @@ -87,8 +102,8 @@ def run_cmd(context, exec_cmd, local=INVOKE_LOCAL, port=None): ) def build(context, cache=True, force_rm=False, hide=False): """Build a Docker image.""" - print(f"Building image {IMAGE_NAME}:{IMAGE_VER}") - command = f"docker build --tag {IMAGE_NAME}:{IMAGE_VER} --build-arg PYTHON_VER={PYTHON_VER} -f Dockerfile ." + print(f"Building image {context.pyntc.image_name}:{context.pyntc.image_ver}") + command = f"docker build --tag {context.pyntc.image_name}:{context.pyntc.image_ver} --build-arg PYTHON_VER={context.pyntc.python_ver} -f Dockerfile ." if not cache: command += " --no-cache" @@ -97,15 +112,15 @@ def build(context, cache=True, force_rm=False, hide=False): result = context.run(command, hide=hide) if result.exited != 0: - print(f"Failed to build image {IMAGE_NAME}:{IMAGE_VER}\nError: {result.stderr}") + print(f"Failed to build image {context.pyntc.image_name}:{context.pyntc.image_ver}\nError: {result.stderr}") @task def clean(context): """Remove the project specific image.""" - print(f"Attempting to forcefully remove image {IMAGE_NAME}:{IMAGE_VER}") - context.run(f"docker rmi {IMAGE_NAME}:{IMAGE_VER} --force") - print(f"Successfully removed image {IMAGE_NAME}:{IMAGE_VER}") + print(f"Attempting to forcefully remove image {context.pyntc.image_name}:{context.pyntc.image_ver}") + context.run(f"docker rmi {context.pyntc.image_name}:{context.pyntc.image_ver} --force") + print(f"Successfully removed image {context.pyntc.image_name}:{context.pyntc.image_ver}") @task @@ -115,78 +130,124 @@ def rebuild(context): build(context, cache=False) -@task(help={"local": "Run locally or within the Docker container"}) -def pytest(context, local=INVOKE_LOCAL, args=""): +@task +def pytest(context, args=""): """Run pytest test cases.""" exec_cmd = f"pytest {args}" - run_cmd(context, exec_cmd, local) - - -@task(help={"local": "Run locally or within the Docker container"}) -def black(context, local=INVOKE_LOCAL): - """Run black to check that Python files adherence to black standards.""" - exec_cmd = "black --check --diff ." - run_cmd(context, exec_cmd, local) + run_command(context, exec_cmd) -@task(help={"local": "Run locally or within the Docker container"}) -def flake8(context, local=INVOKE_LOCAL): - """Run flake8 code analysis.""" - exec_cmd = "flake8 ." - run_cmd(context, exec_cmd, local) +@task(aliases=("a",)) +def autoformat(context): + """Run code autoformatting.""" + ruff(context, action=["format"], fix=True) -@task(help={"local": "Run locally or within the Docker container"}) -def pylint(context, local=INVOKE_LOCAL): - """Run pylint code analysis.""" - exec_cmd = 'find . -name "*.py" | grep -vE "tests/unit" | xargs pylint' - run_cmd(context, exec_cmd, local) +@task( + help={ + "action": "Available values are `['lint', 'format']`. Can be used multiple times. (default: `['lint', 'format']`)", + "target": "File or directory to inspect, repeatable (default: all files in the project will be inspected)", + "fix": "Automatically fix selected actions. May not be able to fix all issues found. (default: False)", + "output_format": "See https://docs.astral.sh/ruff/settings/#output-format for details. (default: `concise`)", + }, + iterable=["action", "target"], +) +def ruff(context, action=None, target=None, fix=False, output_format="concise"): + """Run ruff to perform code formatting and/or linting.""" + if not action: + action = ["lint", "format"] + if not target: + target = ["."] + + exit_code = 0 + + if "format" in action: + command = "ruff format " + if not fix: + command += "--check " + command += " ".join(target) + if not run_command(context, command): + exit_code = 1 + + if "lint" in action: + command = "ruff check " + if fix: + command += "--fix " + command += f"--output-format {output_format} " + command += " ".join(target) + if not run_command(context, command): + exit_code = 1 + + if exit_code != 0: + raise Exit(code=exit_code) -@task(help={"local": "Run locally or within the Docker container"}) -def yamllint(context, local=INVOKE_LOCAL): - """Run yamllint to validate formatting adheres to NTC defined YAML standards.""" - exec_cmd = "yamllint ." - run_cmd(context, exec_cmd, local) +@task +def pylint(context): + """Run pylint for the specified name and Python version. + Args: + context (obj): Used to run specific commands + """ + exec_cmd = 'find . -name "*.py" | grep -vE "tests/unit" | xargs pylint' + run_command(context, exec_cmd) -@task(help={"local": "Run locally or within the Docker container"}) -def pydocstyle(context, local=INVOKE_LOCAL): - """Run pydocstyle to validate docstring formatting adheres to NTC defined standards.""" - exec_cmd = "pydocstyle ." - run_cmd(context, exec_cmd, local) +@task +def yamllint(context): + """Run yamllint to validate formatting adheres to NTC defined YAML standards. -@task(help={"local": "Run locally or within the Docker container"}) -def bandit(context, local=INVOKE_LOCAL): - """Run bandit to validate basic static code security analysis.""" - exec_cmd = "bandit --recursive ./ --configfile .bandit.yml" - run_cmd(context, exec_cmd, local) + Args: + context (obj): Used to run specific commands + """ + exec_cmd = "yamllint ." + run_command(context, exec_cmd) @task def cli(context): - """Enter the image to perform troubleshooting or dev work.""" - dev = f"docker run -it -v {PWD}:/local {IMAGE_NAME}:{IMAGE_VER} /bin/bash" + """Enter the image to perform troubleshooting or dev work. + + Args: + context (obj): Used to run specific commands + """ + dev = f"docker run -it -v {context.pyntc.pwd}:/local {context.pyntc.image_name}:{context.pyntc.image_ver} /bin/bash" context.run(f"{dev}", pty=True) -@task(help={"local": "Run locally or within the Docker container"}) -def tests(context, local=INVOKE_LOCAL): - """Run all tests for this repository.""" - black(context, local) - flake8(context, local) - pylint(context, local) - yamllint(context, local) - pydocstyle(context, local) - bandit(context, local) - pytest(context, local) +@task +def tests(context): + """Run all tests for the specified name and Python version. + + Args: + context (obj): Used to run specific commands + """ + ruff(context) + pylint(context) + yamllint(context) + pytest(context) print("All tests have passed!") @task -def docs(context, local=INVOKE_LOCAL): +def docs(context): """Build and serve docs locally for development.""" exec_cmd = "mkdocs serve -v --dev-addr=0.0.0.0:8001" - run_cmd(context, exec_cmd, local, port="8001:8001") + run_command(context, exec_cmd, port="8001:8001") + + +@task( + help={ + "version": "Version of pyntc to generate the release notes for.", + } +) +def generate_release_notes(context, version=""): + """Generate Release Notes using Towncrier.""" + command = "poetry run towncrier build" + if version: + command += f" --version {version}" + else: + command += " --version `poetry version -s`" + # Due to issues with git repo ownership in the containers, this must always run locally. + context.run(command) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index ff30404a..37acf4f5 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -2,6 +2,7 @@ from unittest import mock import pytest + from pyntc.devices import AIREOSDevice, ASADevice, IOSDevice, IOSXEWLCDevice, supported_devices diff --git a/tests/unit/test_devices/device_mocks/eos/__init__.py b/tests/unit/test_devices/device_mocks/eos/__init__.py index cdd4c38d..71e4e1e1 100644 --- a/tests/unit/test_devices/device_mocks/eos/__init__.py +++ b/tests/unit/test_devices/device_mocks/eos/__init__.py @@ -1,8 +1,9 @@ -import os import json -from pyeapi.eapilib import CommandError as EOSCommandError +import os import re +from pyeapi.eapilib import CommandError as EOSCommandError + CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/tests/unit/test_devices/device_mocks/nxos/__init__.py b/tests/unit/test_devices/device_mocks/nxos/__init__.py index fb82b727..e04a60d9 100644 --- a/tests/unit/test_devices/device_mocks/nxos/__init__.py +++ b/tests/unit/test_devices/device_mocks/nxos/__init__.py @@ -1,5 +1,6 @@ -import os import json +import os + from pynxos.errors import CLIError CURRNENT_DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/tests/unit/test_devices/test_aireos_device.py b/tests/unit/test_devices/test_aireos_device.py index 430c8813..b6a6cda5 100644 --- a/tests/unit/test_devices/test_aireos_device.py +++ b/tests/unit/test_devices/test_aireos_device.py @@ -3,8 +3,8 @@ import pytest -from pyntc.devices import aireos_device as aireos_module from pyntc.devices import AIREOSDevice +from pyntc.devices import aireos_device as aireos_module @pytest.mark.parametrize( @@ -161,7 +161,7 @@ def test_wait_for_ap_image_download_timeout(mock_ap_image_stats, aireos_device): with pytest.raises(aireos_module.FileTransferError) as fte: aireos_device._wait_for_ap_image_download(timeout=1) assert fte.value.message == ( - "Failed waiting for AP image to be transferred to all devices:\n" "Total: 2\nDownloaded: 1" + "Failed waiting for AP image to be transferred to all devices:\nTotal: 2\nDownloaded: 1" ) @@ -480,7 +480,7 @@ def test_disable_wlans_all_fail(mock_disabled_wlans, mock_wlans, aireos_device, aireos_device.disable_wlans("all") assert disable_err.value.message == ( - "Unable to disable WLAN IDs on host\n" "Expected: [5, 15, 16, 20, 21, 22, 24]\n" "Found: [16, 21, 24]\n" + "Unable to disable WLAN IDs on host\nExpected: [5, 15, 16, 20, 21, 22, 24]\nFound: [16, 21, 24]\n" ) @@ -523,7 +523,7 @@ def test_disable_wlans_subset_fail(mock_disabled_wlans, mock_config, aireos_devi aireos_device.disable_wlans([15]) assert disable_err.value.message == ( - "Unable to disable WLAN IDs on host\n" "Expected: [15, 16, 21, 24]\n" "Found: [16, 21, 24]\n" + "Unable to disable WLAN IDs on host\nExpected: [15, 16, 21, 24]\nFound: [16, 21, 24]\n" ) @@ -600,7 +600,7 @@ def test_enable_wlans_all_fail(mock_enabled_wlans, mock_wlans, mock_config, aire aireos_device.enable_wlans("all") assert enable_err.value.message == ( - "Unable to enable WLAN IDs on host\n" "Expected: [5, 15, 16, 20, 21, 22, 24]\n" "Found: [5, 15, 20, 22]\n" + "Unable to enable WLAN IDs on host\nExpected: [5, 15, 16, 20, 21, 22, 24]\nFound: [5, 15, 20, 22]\n" ) @@ -643,7 +643,7 @@ def test_enable_wlans_subset_fail(mock_enabled_wlans, mock_config, aireos_device aireos_device.enable_wlans([16]) assert enable_err.value.message == ( - "Unable to enable WLAN IDs on host\n" "Expected: [5, 15, 16, 20, 22]\n" "Found: [5, 15, 20, 22]\n" + "Unable to enable WLAN IDs on host\nExpected: [5, 15, 16, 20, 22]\nFound: [5, 15, 20, 22]\n" ) diff --git a/tests/unit/test_devices/test_asa_device.py b/tests/unit/test_devices/test_asa_device.py index 226159a0..f89ffb4b 100644 --- a/tests/unit/test_devices/test_asa_device.py +++ b/tests/unit/test_devices/test_asa_device.py @@ -4,8 +4,8 @@ import pytest -from pyntc.devices import asa_device as asa_module from pyntc.devices import ASADevice +from pyntc.devices import asa_device as asa_module from .device_mocks.asa import send_command diff --git a/tests/unit/test_devices/test_ios_device.py b/tests/unit/test_devices/test_ios_device.py index beb7ea34..f466c2be 100644 --- a/tests/unit/test_devices/test_ios_device.py +++ b/tests/unit/test_devices/test_ios_device.py @@ -5,8 +5,8 @@ import mock import pytest -from pyntc.devices import ios_device as ios_module from pyntc.devices import IOSDevice +from pyntc.devices import ios_device as ios_module from pyntc.devices.base_device import RollbackError from .device_mocks.ios import send_command, send_command_expect diff --git a/tests/unit/test_errors.py b/tests/unit/test_errors.py index 5f88e508..2b4edc0b 100644 --- a/tests/unit/test_errors.py +++ b/tests/unit/test_errors.py @@ -55,12 +55,7 @@ def test_command_error(): def test_command_list_error(): error_message = ( - "\n" - "Command fail failed with message: '% invalid command'\n" - "Command List: \n" - "\tcommand 1\n" - "\tfail\n" - "\tcommand 2\n" + "\nCommand fail failed with message: '% invalid command'\nCommand List: \n\tcommand 1\n\tfail\n\tcommand 2\n" ) error_class = ntc_errors.CommandListError error = error_class( @@ -75,7 +70,7 @@ def test_command_list_error(): def test_device_not_active_error(): - expected = "ntc_host is not the active device.\n\n" "device state: standby hot\n" "peer state: active\n" + expected = "ntc_host is not the active device.\n\ndevice state: standby hot\npeer state: active\n" error_class = ntc_errors.DeviceNotActiveError error = error_class("ntc_host", "standby hot", "active") with pytest.raises(error_class) as err: @@ -170,7 +165,7 @@ def test_peer_failed_to_form_error(): raise error assert err.value.message == ( - 'host1 was unable to form a redundancy state of "standby hot" with peer.\n' 'The current state is "disabled".' + 'host1 was unable to form a redundancy state of "standby hot" with peer.\nThe current state is "disabled".' ) diff --git a/tests/unit/test_infra.py b/tests/unit/test_infra.py index 14349898..d1351bd6 100644 --- a/tests/unit/test_infra.py +++ b/tests/unit/test_infra.py @@ -1,11 +1,11 @@ import os + import mock import pytest from pyntc import ntc_device, ntc_device_by_name -from pyntc.errors import UnsupportedDeviceError, ConfFileNotFoundError -from pyntc.devices import EOSDevice, NXOSDevice, IOSDevice - +from pyntc.devices import EOSDevice, IOSDevice, NXOSDevice +from pyntc.errors import ConfFileNotFoundError, UnsupportedDeviceError BAD_DEVICE_TYPE = "238nzsvkn3981" FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "..", "fixtures") diff --git a/tests/unit/test_system_features/test_vlan/mocks/eos/__init__.py b/tests/unit/test_system_features/test_vlan/mocks/eos/__init__.py index 7dcc45b0..4dbc137d 100644 --- a/tests/unit/test_system_features/test_vlan/mocks/eos/__init__.py +++ b/tests/unit/test_system_features/test_vlan/mocks/eos/__init__.py @@ -1,6 +1,5 @@ -import os import json - +import os CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/tests/unit/test_system_features/test_vlan/test_eos_vlan.py b/tests/unit/test_system_features/test_vlan/test_eos_vlan.py index 393ea822..79b915ad 100644 --- a/tests/unit/test_system_features/test_vlan/test_eos_vlan.py +++ b/tests/unit/test_system_features/test_vlan/test_eos_vlan.py @@ -1,9 +1,11 @@ -import mock import unittest -from .mocks.eos import get, getall -from pyntc.devices.system_features.vlans.eos_vlans import EOSVlans +import mock + from pyntc.devices.system_features.vlans.base_vlans import VlanNotInRangeError +from pyntc.devices.system_features.vlans.eos_vlans import EOSVlans + +from .mocks.eos import get, getall class TestEOSVlan(unittest.TestCase): From 848377c76a945276dc42268c72e5ef25949db8ae Mon Sep 17 00:00:00 2001 From: Jeff Kala <48843785+jeffkala@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:55:55 -0600 Subject: [PATCH 06/10] fix docs build and docs around standards, towncrier, etc. (#337) * fix docs build and docs around standards, towncrier, etc. * update ci 'needs' clauses * update to remove mypy * fix mkdocs with more docs best practices * fix mkdocs with more docs best practices * fix griffe types for auto docs adn add code references to mkdocs * fix yamllint * fix yamllint * fix rest of test * add unittest for release notes and docs versions, fix docs requireemnts.txt --- .github/workflows/ci.yml | 11 + docs/admin/install.md | 8 +- .../{version_0_0.md => version_0.0.md} | 0 .../{version_0_14.md => version_0.14.md} | 0 .../{version_0_15.md => version_0.15.md} | 0 .../{version_0_16.md => version_0.16.md} | 0 .../{version_0_17.md => version_0.17.md} | 0 .../{version_0_18.md => version_0.18.md} | 0 .../{version_0_19.md => version_0.19.md} | 0 .../{version_0_20.md => version_0.20.md} | 0 .../{version_1_0.md => version_1.0.md} | 0 .../{version_2_0.md => version_2.0.md} | 0 docs/admin/uninstall.md | 2 +- docs/admin/upgrade.md | 2 +- docs/dev/contributing.md | 39 +- docs/dev/dev_environment.md | 24 +- docs/generate_code_reference_pages.py | 20 + docs/requirements.txt | 22 +- mkdocs.yml | 100 +++-- poetry.lock | 347 ++++++++++-------- pyntc/__init__.py | 8 +- pyntc/devices/aireos_device.py | 67 ++-- pyntc/devices/asa_device.py | 50 +-- pyntc/devices/base_device.py | 32 +- pyntc/devices/eos_device.py | 46 +-- pyntc/devices/f5_device.py | 46 +-- pyntc/devices/ios_device.py | 66 ++-- pyntc/devices/iosxewlc_device.py | 4 +- pyntc/devices/jnpr_device.py | 40 +- pyntc/devices/nxos_device.py | 38 +- .../system_features/vlans/base_vlans.py | 6 +- pyntc/log.py | 6 +- pyntc/utils/converters.py | 12 +- pyntc/utils/templates/__init__.py | 6 +- pyproject.toml | 29 +- tests/unit/test_basic.py | 48 +++ 36 files changed, 645 insertions(+), 434 deletions(-) rename docs/admin/release_notes/{version_0_0.md => version_0.0.md} (100%) rename docs/admin/release_notes/{version_0_14.md => version_0.14.md} (100%) rename docs/admin/release_notes/{version_0_15.md => version_0.15.md} (100%) rename docs/admin/release_notes/{version_0_16.md => version_0.16.md} (100%) rename docs/admin/release_notes/{version_0_17.md => version_0.17.md} (100%) rename docs/admin/release_notes/{version_0_18.md => version_0.18.md} (100%) rename docs/admin/release_notes/{version_0_19.md => version_0.19.md} (100%) rename docs/admin/release_notes/{version_0_20.md => version_0.20.md} (100%) rename docs/admin/release_notes/{version_1_0.md => version_1.0.md} (100%) rename docs/admin/release_notes/{version_2_0.md => version_2.0.md} (100%) create mode 100644 docs/generate_code_reference_pages.py create mode 100644 tests/unit/test_basic.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc6fa2fa..b680ffbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,10 @@ jobs: poetry-version: "1.8.5" - name: "Checking: poetry lock file" run: "poetry lock --check" + needs: + - "ruff-format" + - "ruff-lint" + - "yamllint" yamllint: runs-on: "ubuntu-24.04" env: @@ -83,6 +87,9 @@ jobs: poetry-version: "1.8.5" - name: "Linting: yamllint" run: "poetry run invoke yamllint" + needs: + - "ruff-format" + - "ruff-lint" pylint: runs-on: "ubuntu-24.04" strategy: @@ -118,6 +125,8 @@ jobs: run: "docker image ls" - name: "Linting: Pylint" run: "poetry run invoke pylint" + needs: + - "poetry" pytest: strategy: fail-fast: true @@ -153,6 +162,8 @@ jobs: run: "docker image ls" - name: "Run Tests" run: "poetry run invoke pytest" + needs: + - "poetry" publish_gh: name: "Publish to GitHub" runs-on: "ubuntu-24.04" diff --git a/docs/admin/install.md b/docs/admin/install.md index 6bb1d6dc..7efcc289 100644 --- a/docs/admin/install.md +++ b/docs/admin/install.md @@ -2,21 +2,21 @@ Option 1: Install from PyPI. -``` +```bash pip install pyntc ``` Option 2: Manually install via Poetry. -``` +```bash git clone https://github.com/networktocode/pyntc.git cd pyntc -pip install poetry +curl -sSL https://install.python-poetry.org | python3 - poetry install ``` Option 3: Install from a GitHub branch, such as develop as shown below. ```bash -$ pip install git+https://github.com/networktocode/pyntc.git@develop +pip install git+https://github.com/networktocode/pyntc.git@develop ``` diff --git a/docs/admin/release_notes/version_0_0.md b/docs/admin/release_notes/version_0.0.md similarity index 100% rename from docs/admin/release_notes/version_0_0.md rename to docs/admin/release_notes/version_0.0.md diff --git a/docs/admin/release_notes/version_0_14.md b/docs/admin/release_notes/version_0.14.md similarity index 100% rename from docs/admin/release_notes/version_0_14.md rename to docs/admin/release_notes/version_0.14.md diff --git a/docs/admin/release_notes/version_0_15.md b/docs/admin/release_notes/version_0.15.md similarity index 100% rename from docs/admin/release_notes/version_0_15.md rename to docs/admin/release_notes/version_0.15.md diff --git a/docs/admin/release_notes/version_0_16.md b/docs/admin/release_notes/version_0.16.md similarity index 100% rename from docs/admin/release_notes/version_0_16.md rename to docs/admin/release_notes/version_0.16.md diff --git a/docs/admin/release_notes/version_0_17.md b/docs/admin/release_notes/version_0.17.md similarity index 100% rename from docs/admin/release_notes/version_0_17.md rename to docs/admin/release_notes/version_0.17.md diff --git a/docs/admin/release_notes/version_0_18.md b/docs/admin/release_notes/version_0.18.md similarity index 100% rename from docs/admin/release_notes/version_0_18.md rename to docs/admin/release_notes/version_0.18.md diff --git a/docs/admin/release_notes/version_0_19.md b/docs/admin/release_notes/version_0.19.md similarity index 100% rename from docs/admin/release_notes/version_0_19.md rename to docs/admin/release_notes/version_0.19.md diff --git a/docs/admin/release_notes/version_0_20.md b/docs/admin/release_notes/version_0.20.md similarity index 100% rename from docs/admin/release_notes/version_0_20.md rename to docs/admin/release_notes/version_0.20.md diff --git a/docs/admin/release_notes/version_1_0.md b/docs/admin/release_notes/version_1.0.md similarity index 100% rename from docs/admin/release_notes/version_1_0.md rename to docs/admin/release_notes/version_1.0.md diff --git a/docs/admin/release_notes/version_2_0.md b/docs/admin/release_notes/version_2.0.md similarity index 100% rename from docs/admin/release_notes/version_2_0.md rename to docs/admin/release_notes/version_2.0.md diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md index 0dcf3849..0ccbd7b8 100644 --- a/docs/admin/uninstall.md +++ b/docs/admin/uninstall.md @@ -3,5 +3,5 @@ Uninstall from environment. ```bash -$ pip uninstall pyntc +pip uninstall pyntc ``` diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md index 88c9a334..69a6b98b 100644 --- a/docs/admin/upgrade.md +++ b/docs/admin/upgrade.md @@ -3,5 +3,5 @@ Upgrade from PyPI. ```bash -$ pip install pyntc --upgrade +pip install pyntc --upgrade ``` diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md index eef6e52c..ae2f5cb9 100644 --- a/docs/dev/contributing.md +++ b/docs/dev/contributing.md @@ -4,15 +4,44 @@ Pull requests are welcomed and automatically built and tested against multiple v Except for unit tests, testing is only supported on Python 3.9. -The project is packaged with a light development environment based on `Docker` to help with the local development of the project and to run tests within GitHub Actions. +The project is packaged with a light development environment based on `Docker` to help with the local development of the project and to run tests within GitHub Actions. -The project is following Network to Code software development guidelines and are leveraging the following: +The project is following Network to Code software development guidelines and is leveraging the following: -- Black, Pylint, Bandit, flake8, and pydocstyle for Python linting and formatting. -- pytest, coverage, and unittest for unit tests. +- Python linting and formatting: `pylint` and `ruff`. +- YAML linting is done with `yamllint`. Documentation is built using [mkdocs](https://www.mkdocs.org/). The [Docker based development environment](dev_environment.md#docker-development-environment) can be started by running `invoke docs` [http://localhost:8001](http://localhost:8001) that auto-refreshes when you make any changes to your local files. +## Creating Changelog Fragments + +All pull requests to `next` or `develop` must include a changelog fragment file in the `./changes` directory. To create a fragment, use your GitHub issue number and fragment type as the filename. For example, `2362.added`. Valid fragment types are `added`, `changed`, `deprecated`, `fixed`, `removed`, and `security`. The change summary is added to the file in plain text. Change summaries should be complete sentences, starting with a capital letter and ending with a period, and be in past tense. Each line of the change fragment will generate a single change entry in the release notes. Use multiple lines in the same file if your change needs to generate multiple release notes in the same category. If the change needs to create multiple entries in separate categories, create multiple files. + +!!! example + + **Wrong** + ```plaintext title="changes/1234.fixed" + fix critical bug in documentation + ``` + + **Right** + ```plaintext title="changes/1234.fixed" + Fixed critical bug in documentation. + ``` + +!!! example "Multiple Entry Example" + + This will generate 2 entries in the `fixed` category and one entry in the `changed` category. + + ```plaintext title="changes/1234.fixed" + Fixed critical bug in documentation. + Fixed release notes generation. + ``` + + ```plaintext title="changes/1234.changed" + Changed release notes generation. + ``` + ## Branching Policy The branching policy includes the following tenets: @@ -45,4 +74,4 @@ When a new release is created the following should happen. - A post release PR is created with. - Change the version from `..` to `..-beta` pyproject.toml. - Set the PR to the `develop`. - - Once tests pass, merge. \ No newline at end of file + - Once tests pass, merge. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 23292492..c67c06a1 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -32,7 +32,6 @@ Once you have Poetry and Docker installed you can run the following commands (in ```shell poetry shell poetry install -cp development/creds.example.env development/creds.env invoke build invoke start ``` @@ -51,7 +50,7 @@ Poetry is used in lieu of the "virtualenv" commands and is leveraged in both env The `pyproject.toml` file outlines all of the relevant dependencies for the project: - `tool.poetry.dependencies` - the main list of dependencies. -- `tool.poetry.dev-dependencies` - development dependencies, to facilitate linting, testing, and documentation building. +- `tool.poetry.group.dev.dependencies` - development dependencies, to facilitate linting, testing, and documentation building. The `poetry shell` command is used to create and enable a virtual environment managed by Poetry, so all commands ran going forward are executed within the virtual environment. This is similar to running the `source venv/bin/activate` command with virtualenvs. To install project dependencies in the virtual environment, you should run `poetry install` - this will install **both** project and development dependencies. @@ -82,21 +81,18 @@ Each command can be executed with `invoke `. Each command also has its ### Utility ``` - cli Enter the image to perform troubleshooting or dev work. - clean-container Remove stopped containers that source for image `pyntc:` + cli Enter the image to perform troubleshooting or dev work. + clean Remove stopped containers that source for image `pyntc:` + generate-release-notes Generate Release Notes using Towncrier. ``` ### Testing ``` - bandit Run bandit to validate basic static code security analysis. - black Run black to check that Python files adhere to its style standards. - coverage Run the coverage report against pytest. - flake8 Run flake8 to check that Python files adhere to its style standards. - mypy Run mypy to validate typing-hints. - pylint Run pylint code analysis. - pydocstyle Run pydocstyle to validate docstring formatting adheres to NTC defined standards. - pytest Run pytest for the specified name and Python version. - tests Run all tests for the specified name and Python version. - yamllint Run yamllint to validate formatting adheres to NTC defined YAML standards. + autoformat (a) Run code autoformatting. + pylint Run pylint for the specified name and Python version. + ruff Run ruff to perform code formatting and/or linting. + pytest Run pytest for the specified name and Python version. + tests Run all tests for the specified name and Python version. + yamllint Run yamllint to validate formatting adheres to NTC defined YAML standards. ``` \ No newline at end of file diff --git a/docs/generate_code_reference_pages.py b/docs/generate_code_reference_pages.py new file mode 100644 index 00000000..0f1bed31 --- /dev/null +++ b/docs/generate_code_reference_pages.py @@ -0,0 +1,20 @@ +"""Generate code reference pages.""" + +from pathlib import Path + +import mkdocs_gen_files + +for file_path in Path("pyntc").rglob("*.py"): + module_path = file_path.with_suffix("") + doc_path = file_path.with_suffix(".md") + full_doc_path = Path("code-reference", doc_path) + + parts = list(module_path.parts) + if parts[-1] == "__init__": + parts = parts[:-1] + + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + IDENTIFIER = ".".join(parts) + print(f"::: {IDENTIFIER}", file=fd) + + mkdocs_gen_files.set_edit_path(full_doc_path, file_path) diff --git a/docs/requirements.txt b/docs/requirements.txt index c4f6af12..e5187d1d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,9 +1,13 @@ -mkdocs==1.6.0 -# Material for MkDocs theme -mkdocs-material==9.5.32 -# Render custom markdown for version added/changed/remove notes -mkdocs-version-annotations==1.0.0 -# Automatic documentation from sources, for MkDocs -mkdocstrings==0.25.2 -mkdocstrings-python==1.10.8 -griffe==1.1.1 \ No newline at end of file +mkdocs==1.6.1 +markdown-data-tables==1.0.0 +markdown-version-annotations==1.0.1 +mkdocs-gen-files==0.5.0 +mkdocs-glightbox==0.4.0 +mkdocs-macros-plugin==1.3.7 +mkdocs-material==9.6.15 +mkdocs-redirects==1.2.2 +mkdocs-section-index==0.3.10 +mkdocs-redirects==1.2.2 +mkdocs-section-index==0.3.10 +mkdocstrings==0.27.0 +mkdocstrings-python==1.13.0 diff --git a/mkdocs.yml b/mkdocs.yml index 85eb9e2f..20315471 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,13 +14,16 @@ theme: - "python" - "yaml" features: - - "navigation.tracking" + - "content.code.annotate" + - "content.code.copy" + - "content.tabs.link" + - "navigation.footer" - "navigation.tabs" - "navigation.tabs.sticky" - - "search.suggest" + - "navigation.tracking" - "search.highlight" - "search.share" - - "navigation.indexes" + - "search.suggest" favicon: "assets/favicon.ico" logo: "assets/networktocode_logo.svg" palette: @@ -39,15 +42,16 @@ theme: toggle: icon: "material/weather-night" name: "Switch to light mode" + +validation: + absolute_links: "warn" + anchors: "warn" + omitted_files: "warn" + unrecognized_links: "warn" + extra_css: - "assets/extra.css" -# needed for RTD version flyout menu -# jquery is not (yet) injected by RTD automatically and it might be dropped -# as a dependency in the future -extra_javascript: - - "https://code.jquery.com/jquery-3.6.0.min.js" - extra: generator: false ntc_sponsor: true @@ -68,29 +72,51 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: + - "markdown_version_annotations": + admonition_tag: "???" - "admonition" - "toc": permalink: true - "attr_list" + - "markdown_data_tables": + base_path: "docs" - "md_in_html" + - "pymdownx.details" + # Need pymdownx.emoji for Grid icon search + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - "pymdownx.highlight": anchor_linenums: true - "pymdownx.inlinehilite" - "pymdownx.snippets" - - "pymdownx.superfences" - - "footnotes" + - "pymdownx.superfences": + custom_fences: + - name: "mermaid" + class: "mermaid" + format: !!python/name:pymdownx.superfences.fence_code_format + - "pymdownx.tabbed": + "alternate_style": true + - "pymdownx.tilde" + plugins: - "search" - - "mkdocs-version-annotations" + - "gen-files": + scripts: + - "docs/generate_code_reference_pages.py" + - "glightbox": + manual: true # See https://blueswen.github.io/mkdocs-glightbox/flexibility/enable-by-image-or-page/ + - "section-index" - "mkdocstrings": default_handler: "python" handlers: python: paths: ["."] options: + heading_level: 1 show_root_heading: true -watch: - - "README.md" + show_root_members_full_path: true + show_source: false nav: - Overview: "index.md" @@ -104,17 +130,43 @@ nav: - Uninstall: "admin/uninstall.md" - Release Notes: - "admin/release_notes/index.md" - - v0.0: "admin/release_notes/version_0_0.md" - - v0.14: "admin/release_notes/version_0_14.md" - - v0.15: "admin/release_notes/version_0_15.md" - - v0.16: "admin/release_notes/version_0_16.md" - - v0.17: "admin/release_notes/version_0_17.md" - - v0.18: "admin/release_notes/version_0_18.md" - - v0.19: "admin/release_notes/version_0_19.md" - - v0.20: "admin/release_notes/version_0_20.md" - - v1.0: "admin/release_notes/version_1_0.md" - - v2.0: "admin/release_notes/version_2_0.md" + - v0.0: "admin/release_notes/version_0.0.md" + - v0.14: "admin/release_notes/version_0.14.md" + - v0.15: "admin/release_notes/version_0.15.md" + - v0.16: "admin/release_notes/version_0.16.md" + - v0.17: "admin/release_notes/version_0.17.md" + - v0.18: "admin/release_notes/version_0.18.md" + - v0.19: "admin/release_notes/version_0.19.md" + - v0.20: "admin/release_notes/version_0.20.md" + - v1.0: "admin/release_notes/version_1.0.md" + - v2.0: "admin/release_notes/version_2.0.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" + - Code Reference: + - pyntc: "code-reference/pyntc/__init__.md" + - pyntc.errors: "code-reference/pyntc/errors.md" + - pyntc.log: "code-reference/pyntc/log.md" + - pyntc.devices: "code-reference/pyntc/devices/__init__.md" + - pyntc.devices.aireos_device: "code-reference/pyntc/devices/aireos_device.md" + - pyntc.devices.asa_device: "code-reference/pyntc/devices/asa_device.md" + - pyntc.devices.base_device: "code-reference/pyntc/devices/base_device.md" + - pyntc.devices.eos_device: "code-reference/pyntc/devices/eos_device.md" + - pyntc.devices.f5_device: "code-reference/pyntc/devices/f5_device.md" + - pyntc.devices.ios_device: "code-reference/pyntc/devices/ios_device.md" + - pyntc.devices.iosxewlc_device: "code-reference/pyntc/devices/iosxewlc_device.md" + - pyntc.devices.jnpr_device: "code-reference/pyntc/devices/jnpr_device.md" + - pyntc.devices.nxos_device: "code-reference/pyntc/devices/nxos_device.md" + - pyntc.devices.system_features: "code-reference/pyntc/devices/system_features/__init__.md" + - pyntc.devices.system_features.base_feature: "code-reference/pyntc/devices/system_features/base_feature.md" + - pyntc.devices.system_features.vlans: "code-reference/pyntc/devices/system_features/vlans/__init__.md" + - pyntc.devices.system_features.vlans.base_vlans: "code-reference/pyntc/devices/system_features/vlans/base_vlans.md" + - pyntc.devices.system_features.vlans.eos_vlans: "code-reference/pyntc/devices/system_features/vlans/eos_vlans.md" + - pyntc.devices.tables: "code-reference/pyntc/devices/tables/__init__.md" + - pyntc.devices.juniper: "code-reference/pyntc/devices/tables/jnpr/__init__.md" + - pyntc.devices.juniper.loopback: "code-reference/pyntc/devices/tables/jnpr/loopback.md" + - pyntc.utils: "code-reference/pyntc/utils/__init__.md" + - pyntc.utils.converters: "code-reference/pyntc/utils/converters.md" + - pyntc.utils.templates: "code-reference/pyntc/utils/templates/__init__.md" + - pyntc.arch_decision: "dev/arch_decision.md" diff --git a/poetry.lock b/poetry.lock index e410af3e..006610a8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -47,6 +47,25 @@ files = [ [package.extras] dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +[[package]] +name = "backrefs" +version = "5.9" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +files = [ + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, +] + +[package.extras] +extras = ["regex"] + [[package]] name = "bcrypt" version = "4.3.0" @@ -477,6 +496,17 @@ files = [ [package.dependencies] colorama = ">=0.4" +[[package]] +name = "hjson" +version = "3.1.0" +description = "Hjson, a user interface for JSON." +optional = false +python-versions = "*" +files = [ + {file = "hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89"}, + {file = "hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75"}, +] + [[package]] name = "idna" version = "3.10" @@ -781,6 +811,20 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markdown-version-annotations" +version = "1.0.1" +description = "Markdown plugin to add custom admonitions for documenting version differences" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "markdown_version_annotations-1.0.1-py3-none-any.whl", hash = "sha256:6df0b2ac08bab906c8baa425f59fc0fe342fbe8b3917c144fb75914266b33200"}, + {file = "markdown_version_annotations-1.0.1.tar.gz", hash = "sha256:620aade507ef175ccfb2059db152a34c6a1d2add28c2be16ea4de38d742e6132"}, +] + +[package.dependencies] +markdown = ">=3.3.7,<4.0.0" + [[package]] name = "markupsafe" version = "3.0.2" @@ -931,6 +975,20 @@ Markdown = ">=3.3" markupsafe = ">=2.0.1" mkdocs = ">=1.1" +[[package]] +name = "mkdocs-gen-files" +version = "0.5.0" +description = "MkDocs plugin to programmatically generate documentation pages during the build" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea"}, + {file = "mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc"}, +] + +[package.dependencies] +mkdocs = ">=1.0.3" + [[package]] name = "mkdocs-get-deps" version = "0.2.0" @@ -948,32 +1006,68 @@ mergedeep = ">=1.3.4" platformdirs = ">=2.2.0" pyyaml = ">=5.1" +[[package]] +name = "mkdocs-glightbox" +version = "0.4.0" +description = "MkDocs plugin supports image lightbox with GLightbox." +optional = false +python-versions = "*" +files = [ + {file = "mkdocs-glightbox-0.4.0.tar.gz", hash = "sha256:392b34207bf95991071a16d5f8916d1d2f2cd5d5bb59ae2997485ccd778c70d9"}, + {file = "mkdocs_glightbox-0.4.0-py3-none-any.whl", hash = "sha256:e0107beee75d3eb7380ac06ea2d6eac94c999eaa49f8c3cbab0e7be2ac006ccf"}, +] + +[[package]] +name = "mkdocs-macros-plugin" +version = "1.3.7" +description = "Unleash the power of MkDocs with macros and variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_macros_plugin-1.3.7-py3-none-any.whl", hash = "sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59"}, + {file = "mkdocs_macros_plugin-1.3.7.tar.gz", hash = "sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523"}, +] + +[package.dependencies] +hjson = "*" +jinja2 = "*" +mkdocs = ">=0.17" +packaging = "*" +pathspec = "*" +python-dateutil = "*" +pyyaml = "*" +super-collections = "*" +termcolor = "*" + +[package.extras] +test = ["mkdocs-d2-plugin", "mkdocs-include-markdown-plugin", "mkdocs-macros-test", "mkdocs-material (>=6.2)", "mkdocs-test"] + [[package]] name = "mkdocs-material" -version = "9.5.32" +version = "9.6.15" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.32-py3-none-any.whl", hash = "sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f"}, - {file = "mkdocs_material-9.5.32.tar.gz", hash = "sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120"}, + {file = "mkdocs_material-9.6.15-py3-none-any.whl", hash = "sha256:ac969c94d4fe5eb7c924b6d2f43d7db41159ea91553d18a9afc4780c34f2717a"}, + {file = "mkdocs_material-9.6.15.tar.gz", hash = "sha256:64adf8fa8dba1a17905b6aee1894a5aafd966d4aeb44a11088519b0f5ca4f1b5"}, ] [package.dependencies] babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" +jinja2 = ">=3.1,<4.0" markdown = ">=3.2,<4.0" mkdocs = ">=1.6,<2.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pygments = ">=2.16,<3.0" pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" requests = ">=2.26,<3.0" [package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] @@ -989,36 +1083,53 @@ files = [ ] [[package]] -name = "mkdocs-version-annotations" -version = "1.0.0" -description = "MkDocs plugin to add custom admonitions for documenting version differences" +name = "mkdocs-redirects" +version = "1.2.2" +description = "A MkDocs plugin for dynamic page redirects to prevent broken links" optional = false -python-versions = ">=3.7,<4.0" +python-versions = ">=3.8" files = [ - {file = "mkdocs-version-annotations-1.0.0.tar.gz", hash = "sha256:6786024b37d27b330fda240b76ebec8e7ce48bd5a9d7a66e99804559d088dffa"}, - {file = "mkdocs_version_annotations-1.0.0-py3-none-any.whl", hash = "sha256:385004eb4a7530dd87a227e08cd907ce7a8fe21fdf297720a4149c511bcf05f5"}, + {file = "mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5"}, + {file = "mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095"}, ] +[package.dependencies] +mkdocs = ">=1.1.1" + +[[package]] +name = "mkdocs-section-index" +version = "0.3.10" +description = "MkDocs plugin to allow clickable sections that lead to an index page" +optional = false +python-versions = ">=3.9" +files = [ + {file = "mkdocs_section_index-0.3.10-py3-none-any.whl", hash = "sha256:bc27c0d0dc497c0ebaee1fc72839362aed77be7318b5ec0c30628f65918e4776"}, + {file = "mkdocs_section_index-0.3.10.tar.gz", hash = "sha256:a82afbda633c82c5568f0e3b008176b9b365bf4bd8b6f919d6eff09ee146b9f8"}, +] + +[package.dependencies] +mkdocs = ">=1.2" + [[package]] name = "mkdocstrings" -version = "0.25.2" +version = "0.27.0" description = "Automatic documentation from sources, for MkDocs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mkdocstrings-0.25.2-py3-none-any.whl", hash = "sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc"}, - {file = "mkdocstrings-0.25.2.tar.gz", hash = "sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc"}, + {file = "mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332"}, + {file = "mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657"}, ] [package.dependencies] click = ">=7.0" importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" -Markdown = ">=3.3" +Markdown = ">=3.6" MarkupSafe = ">=1.1" mkdocs = ">=1.4" -mkdocs-autorefs = ">=0.3.1" -platformdirs = ">=2.2.0" +mkdocs-autorefs = ">=1.2" +platformdirs = ">=2.2" pymdown-extensions = ">=6.3" typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} @@ -1029,18 +1140,19 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.10.8" +version = "1.13.0" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mkdocstrings_python-1.10.8-py3-none-any.whl", hash = "sha256:bb12e76c8b071686617f824029cb1dfe0e9afe89f27fb3ad9a27f95f054dcd89"}, - {file = "mkdocstrings_python-1.10.8.tar.gz", hash = "sha256:5856a59cbebbb8deb133224a540de1ff60bded25e54d8beacc375bb133d39016"}, + {file = "mkdocstrings_python-1.13.0-py3-none-any.whl", hash = "sha256:b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97"}, + {file = "mkdocstrings_python-1.13.0.tar.gz", hash = "sha256:2dbd5757e8375b9720e81db16f52f1856bf59905428fd7ef88005d1370e2f64c"}, ] [package.dependencies] griffe = ">=0.49" -mkdocstrings = ">=0.25" +mkdocs-autorefs = ">=1.2" +mkdocstrings = ">=0.26" [[package]] name = "mock" @@ -1286,13 +1398,13 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.16" +version = "10.16.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" files = [ - {file = "pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2"}, - {file = "pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de"}, + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] @@ -1484,109 +1596,6 @@ files = [ [package.dependencies] pyyaml = "*" -[[package]] -name = "regex" -version = "2024.11.6" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, -] - [[package]] name = "requests" version = "2.32.4" @@ -1627,19 +1636,18 @@ fixture = ["fixtures"] [[package]] name = "rich" -version = "14.0.0" +version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ - {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, - {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -1714,29 +1722,29 @@ files = [ [[package]] name = "ruff" -version = "0.12.3" +version = "0.12.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.12.3-py3-none-linux_armv6l.whl", hash = "sha256:47552138f7206454eaf0c4fe827e546e9ddac62c2a3d2585ca54d29a890137a2"}, - {file = "ruff-0.12.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a9153b000c6fe169bb307f5bd1b691221c4286c133407b8827c406a55282041"}, - {file = "ruff-0.12.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fa6b24600cf3b750e48ddb6057e901dd5b9aa426e316addb2a1af185a7509882"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2506961bf6ead54887ba3562604d69cb430f59b42133d36976421bc8bd45901"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4faaff1f90cea9d3033cbbcdf1acf5d7fb11d8180758feb31337391691f3df0"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40dced4a79d7c264389de1c59467d5d5cefd79e7e06d1dfa2c75497b5269a5a6"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0262d50ba2767ed0fe212aa7e62112a1dcbfd46b858c5bf7bbd11f326998bafc"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12371aec33e1a3758597c5c631bae9a5286f3c963bdfb4d17acdd2d395406687"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:560f13b6baa49785665276c963edc363f8ad4b4fc910a883e2625bdb14a83a9e"}, - {file = "ruff-0.12.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023040a3499f6f974ae9091bcdd0385dd9e9eb4942f231c23c57708147b06311"}, - {file = "ruff-0.12.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:883d844967bffff5ab28bba1a4d246c1a1b2933f48cb9840f3fdc5111c603b07"}, - {file = "ruff-0.12.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2120d3aa855ff385e0e562fdee14d564c9675edbe41625c87eeab744a7830d12"}, - {file = "ruff-0.12.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b16647cbb470eaf4750d27dddc6ebf7758b918887b56d39e9c22cce2049082b"}, - {file = "ruff-0.12.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e1417051edb436230023575b149e8ff843a324557fe0a265863b7602df86722f"}, - {file = "ruff-0.12.3-py3-none-win32.whl", hash = "sha256:dfd45e6e926deb6409d0616078a666ebce93e55e07f0fb0228d4b2608b2c248d"}, - {file = "ruff-0.12.3-py3-none-win_amd64.whl", hash = "sha256:a946cf1e7ba3209bdef039eb97647f1c77f6f540e5845ec9c114d3af8df873e7"}, - {file = "ruff-0.12.3-py3-none-win_arm64.whl", hash = "sha256:5f9c7c9c8f84c2d7f27e93674d27136fbf489720251544c4da7fb3d742e011b1"}, - {file = "ruff-0.12.3.tar.gz", hash = "sha256:f1b5a4b6668fd7b7ea3697d8d98857390b40c1320a63a178eee6be0899ea2d77"}, + {file = "ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303"}, + {file = "ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb"}, + {file = "ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e"}, + {file = "ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606"}, + {file = "ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8"}, + {file = "ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa"}, + {file = "ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5"}, + {file = "ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4"}, + {file = "ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77"}, + {file = "ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f"}, + {file = "ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69"}, + {file = "ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71"}, ] [[package]] @@ -1784,6 +1792,23 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "super-collections" +version = "0.5.3" +description = "file: README.md" +optional = false +python-versions = ">=3.8" +files = [ + {file = "super_collections-0.5.3-py3-none-any.whl", hash = "sha256:907d35b25dc4070910e8254bf2f5c928348af1cf8a1f1e8259e06c666e902cff"}, + {file = "super_collections-0.5.3.tar.gz", hash = "sha256:94c1ec96c0a0d5e8e7d389ed8cde6882ac246940507c5e6b86e91945c2968d46"}, +] + +[package.dependencies] +hjson = "*" + +[package.extras] +test = ["pytest (>=7.0)"] + [[package]] name = "tabulate" version = "0.9.0" @@ -1798,6 +1823,20 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "termcolor" +version = "3.1.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"}, + {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "textfsm" version = "1.1.3" @@ -2039,4 +2078,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "f3498949bec46a5d33bf66adc7fa02a08974938e90335aa39b21e7ebbb48fa5c" +content-hash = "0832b768dd99110ac087b19fa566bc39ac2d223002c8360993226b83b27b76d6" diff --git a/pyntc/__init__.py b/pyntc/__init__.py index 338b27f1..ff917746 100644 --- a/pyntc/__init__.py +++ b/pyntc/__init__.py @@ -29,13 +29,13 @@ def ntc_device(device_type, *args, **kwargs): The ``*args`` and ``**kwargs`` are passed directly to the device initializer. Arguments: - device_type (string): A valid device_type + device_type (str): A valid device_type listed in `pyntc.devices.supported_devices` - args: Positional arguments to pass to the device initializer. - kwargs: Keyword arguments to pass to the device initializer. + args (tuple): Positional arguments to pass to the device initializer. + kwargs (dict): Keyword arguments to pass to the device initializer. Returns: - An instance of a subclass of ``pyntc.devices.BaseDevice``. + (pyntc.devices.BaseDevice): An instance of a subclass of ``pyntc.devices.BaseDevice``. Raises: UnsupportedDeviceError: if the device_type is unsupported. diff --git a/pyntc/devices/aireos_device.py b/pyntc/devices/aireos_device.py index e29fcfd0..f77b6103 100644 --- a/pyntc/devices/aireos_device.py +++ b/pyntc/devices/aireos_device.py @@ -48,7 +48,7 @@ def convert_filename_to_version(filename): filename (str): The name of the file downloaded from Cisco. Returns: - str: The version number. + (str): The version number. Example: >>> version = convert_filename_to_version("AIR-CT5520-K9-8-8-125-0.aes") @@ -83,7 +83,7 @@ def __init__( # nosec secret (str): The password to escalate privilege on the device. port (int): The port to use to establish the connection. Defaults to 22. confirm_active (bool): Determines if device's high availability state should be validated before leaving connection open. - **kwargs: Additional keyword arguments for device customization. + **kwargs (dict): Additional keyword arguments for device customization. """ super().__init__(host, username, password, device_type="cisco_aireos_ssh") self.native = None @@ -393,7 +393,7 @@ def ap_boot_options(self): Boot Options for all APs associated with the controller. Returns: - dict: The name of each AP are the keys, and the values are the primary and backup values. + (dict): The name of each AP are the keys, and the values are the primary and backup values. Example: >>> device = AIREOSDevice(**connection_args) @@ -431,7 +431,7 @@ def ap_image_stats(self): Stats of downloading the the image to all APs. Returns: - dict: The AP count, and the downloaded, unsupported, and failed APs. + (dict): The AP count, and the downloaded, unsupported, and failed APs. Example: >>> device = AIREOSDevice(**connection_args) @@ -476,7 +476,7 @@ def boot_options(self): Images that are candidates for booting on reload. Returns: - dict: The boot options on the device. The "sys" key is the expected image on reload. + (dict): The boot options on the device. The "sys" key is the expected image on reload. Example: >>> device = AIREOSDevice(**connection_args) @@ -541,11 +541,11 @@ def config(self, command, **netmiko_args): Args: command (str|list): The command or commands to send to the device. - **netmiko_args: Any argument supported by ``netmiko.base_connection.BaseConnection.send_config_set``. + **netmiko_args (dict): Any argument supported by ``netmiko.base_connection.BaseConnection.send_config_set``. Returns: - str: When ``command`` is a str, the config session input and ouput from sending ``command``. - list: When ``command`` is a list, the config session input and ouput from sending ``command``. + (str): When ``command`` is a str, the config session input and ouput from sending ``command``. + (list): When ``command`` is a list, the config session input and ouput from sending ``command``. Raises: TypeError: When sending an argument in ``**netmiko_args`` that is not supported. @@ -619,7 +619,7 @@ def confirm_is_active(self): Confirm that the device is either standalone or the active device in a high availability cluster. Returns: - bool: True when the device is considered active. + (bool): True when the device is considered active. Rasies: DeviceNotActiveError: When the device is not considered the active device. @@ -661,7 +661,7 @@ def connected(self): Get connection status of the device. Returns: - bool: True if the device is connected, else False. + (bool): True if the device is connected, else False. """ log.debug("Host %s: Connection status %s.", self.host, self._connected) return self._connected @@ -728,7 +728,7 @@ def disabled_wlans(self): # noqa: D403 IDs for all disabled WLANs. Returns: - list: Disabled WLAN IDs. + (list): Disabled WLAN IDs. Example: >>> device = AIREOSDevice(**connection_args) @@ -754,7 +754,7 @@ def enable(self): Ensure device is in enable mode. Returns: - None: Device prompt is set to enable mode. + (None): Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native.check_enable_mode(): @@ -821,7 +821,7 @@ def enabled_wlans(self): # noqa: D403 IDs for all enabled WLANs. Returns: - list: Enabled WLAN IDs. + (list): Enabled WLAN IDs. Example: >>> device = AIREOSDevice(**connection_args) @@ -875,7 +875,7 @@ def file_copy( read_timeout (int, optional): The Netmiko read_timeout to wait for device to complete transfer. Defaults to 1000. Returns: - bool: True when the file was transferred, False when the file is deemed to already be on the device. + (bool): True when the file was transferred, False when the file is deemed to already be on the device. Raises: FileTransferError: When an error is detected in transferring the file. @@ -955,7 +955,7 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): src (str): The path to the file to be copied to the device. dest (str, optional): The name to use for storing the file on the device. Defaults to use the name of the ``src`` file. - kwargs: Any additional arguments supported by Netmiko's ``file_copy`` method. + kwargs (dict): Any additional arguments supported by Netmiko's ``file_copy`` method. Raises: NotImplementedError: Function currently not implemented. @@ -984,7 +984,7 @@ def install_os(self, image_name, controller="both", save_config=True, disable_wl vendor_specifics (dict): Any vendor specific arguments to pass to the install method. Returns: - bool: True when the install is successful, False when the version is deemed to already be running. + (bool): True when the install is successful, False when the version is deemed to already be running. Raises: OSInstallError: When the device is not booted with the specified image after reload. @@ -1047,7 +1047,7 @@ def is_active(self): Determine if the current processor is the active processor. Returns: - bool: True if the processor is active or does not support HA, else False. + (bool): True if the processor is active or does not support HA, else False. Example: >>> device = AIREOSDevice(**connection_args) @@ -1115,8 +1115,8 @@ def peer_redundancy_state(self): Determine the redundancy state of the peer processor. Returns: - str: The redundancy state of the peer processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the peer processor. + (None): When the processor does not support redundancy. Example: >>> device = AIREOSDevice(**connection_args) @@ -1142,10 +1142,10 @@ def reboot(self, wait_for_reload=False, controller="self", save_config=True, **k Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. controller (str): Which controller(s) to reboot (only applies to HA pairs). save_config (bool): Whether the configuration should be saved before reload. - kwargs: Additional arguments that are not used, but are accepted for backwards compatibility. + kwargs (dict): Additional arguments that are not used, but are accepted for backwards compatibility. Raises: ReloadTimeoutError: When the device is still unreachable after the timeout period. @@ -1192,7 +1192,7 @@ def redundancy_mode(self): Get operating redundancy mode of the controller. Returns: - str: The redundancy mode the device is operating in. + (str): The redundancy mode the device is operating in. Example: >>> device = AIREOSDevice(**connection_args) @@ -1211,8 +1211,8 @@ def redundancy_state(self): Determine the redundancy state of the current processor. Returns: - str: The redundancy state of the current processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the current processor. + (None): When the processor does not support redundancy. Example: >>> device = AIREOSDevice(**connection_args) @@ -1256,7 +1256,7 @@ def save(self): Save the configuration on the device. Returns: - bool: True if the save command did not fail. + (bool): True if the save command did not fail. Example: >>> device = AIREOSDevice(**connection_args) @@ -1273,7 +1273,7 @@ def set_boot_options(self, image_name, **vendor_specifics): Args: image_name (str): The version to boot into on next reload. - **vendor_specifics: Additional vendor-specific arguments (unused). + **vendor_specifics (dict): Additional vendor-specific arguments (unused). Raises: NTCFileNotFoundError: When the version is not listed in ``boot_options``. @@ -1317,11 +1317,11 @@ def show(self, command, expect_string=None, **netmiko_args): Args: command (str|list): The commands to send to the device. expect_string (str): The expected prompt after running the command. - **netmiko_args: Any argument supported by ``netmiko.ConnectHandler.send_command``. + **netmiko_args (dict): Any argument supported by ``netmiko.ConnectHandler.send_command``. Returns: - str: When ``command`` is str, the data returned from the device. - list: When ``command`` is list, the data returned from the device for each command. + (str): When ``command`` is str, the data returned from the device. + (list): When ``command`` is list, the data returned from the device for each command. Raises: TypeError: When sending an argument in ``**netmiko_args`` that is not supported. @@ -1409,10 +1409,9 @@ def transfer_image_to_ap(self, image): Args: image (str): The image that should be sent to the APs. - timeout (int): Removed, The max time to wait for all APs to download the image. Returns: - bool: True if AP images are transferred or swapped, False otherwise. + (bool): True if AP images are transferred or swapped, False otherwise. Example: >>> device = AIREOSDevice(**connection_args) @@ -1486,7 +1485,7 @@ def uptime(self): Get uptime of the device in seconds. Returns: - int: The number of seconds the device has been up. + (int): The number of seconds the device has been up. Example: >>> device = AIREOSDevice(**connection_args) @@ -1507,7 +1506,7 @@ def uptime_string(self): Get uptime of the device as a string in the format is dd::hh::mm. Returns: - str: The uptime of the device. + (str): The uptime of the device. Example: >>> device = AIREOSDevice(**connection_args) @@ -1524,7 +1523,7 @@ def wlans(self): All configured WLANs. Returns: - dict: WLAN IDs mapped to their operational data. + (dict): WLAN IDs mapped to their operational data. Example: >>> device = AIREOSDevice(**connection_args) diff --git a/pyntc/devices/asa_device.py b/pyntc/devices/asa_device.py index b66452fd..03ea207c 100644 --- a/pyntc/devices/asa_device.py +++ b/pyntc/devices/asa_device.py @@ -118,7 +118,7 @@ def _get_file_system(self): """Determine the default file system or directory for device. Returns: - str: The name of the default file system or directory for the device. + (str): The name of the default file system or directory for the device. Raises: FileSystemNotFound: When the module is unable to determine the default file system. @@ -143,7 +143,7 @@ def _get_ipv4_addresses(self, host: str) -> Dict[str, List[IPv4Address]]: host (str): Whether to get IP Addresses for `self` or `peer` device. Returns: - dict: The list of ``ip_interface`` objects mapped to their associated interface. + (dict): The list of ``ip_interface`` objects mapped to their associated interface. Example: >>> dev = ASADevice(**connection_args) @@ -175,7 +175,7 @@ def _get_ipv6_addresses(self, host: str) -> Dict[str, List[IPv6Address]]: host (str): Whether to get IP Addresses for `self` or `peer` device. Returns: - dict: The list of ``ip_interface`` objects mapped to their associated interface. + (dict): The list of ``ip_interface`` objects mapped to their associated interface. Example: >>> dev = ASADevice(**connection_args) @@ -364,7 +364,7 @@ def boot_options(self): Determine boot image. Returns: - dict: Key: 'sys' Value: Current boot image. + (dict): Key: 'sys' Value: Current boot image. """ show_boot_out = self.show("show boot | i BOOT variable") # Improve regex to get only the first boot $var in the sequence! @@ -449,7 +449,7 @@ def enable(self): """Ensure device is in enable mode. Returns: - None: Device prompt is set to enable mode. + (None): Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native.check_enable_mode(): @@ -562,7 +562,7 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None): Defaults to discover the default directory of the device. Returns: - bool: True if the file exists on the device and the md5 hashes match. Otherwise, false. + (bool): True if the file exists on the device and the md5 hashes match. Otherwise, false. Example: >>> status = file_copy_remote_exists("path/to/asa-image.bin") @@ -594,7 +594,7 @@ def install_os(self, image_name, **vendor_specifics): OSInstallError: Message stating the end device could not boot into the new image. Returns: - bool: True if new image is installed correctly. False if device is already running image_name. + (bool): True if new image is installed correctly. False if device is already running image_name. """ timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): @@ -704,7 +704,7 @@ def is_active(self): Determine if the current processor is the active processor. Returns: - bool: True if the processor is active or does not support HA, else False. + (bool): True if the processor is active or does not support HA, else False. Example: >>> device = ASADevice(**connection_args) @@ -825,8 +825,8 @@ def peer_redundancy_state(self): common state will be returned. Returns: - str: The redundancy state of the peer processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the peer processor. + (None): When the processor does not support redundancy. Example: >>> device = ASADevice(**connection_args) @@ -863,7 +863,7 @@ def reboot(self, wait_for_reload=False, **kwargs): Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. kwargs (dict): Additional arguments to pass to the reboot method. Raises: @@ -936,7 +936,7 @@ def redundancy_mode(self): Operating redundancy mode of the device. Returns: - str: The redundancy mode the device is operating in. + (str): The redundancy mode the device is operating in. If the command is not supported, then "n/a" is returned. Example: @@ -965,8 +965,8 @@ def redundancy_state(self): common state will be returned. Returns: - str: The redundancy state of the processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the processor. + (None): When the processor does not support redundancy. Example: >>> device = ASADevice(**connection_args) @@ -1016,7 +1016,7 @@ def running_config(self): Get current running config on device. Returns: - str: Running configuration on device. + (str): Running configuration on device. """ return self.show("show running-config") @@ -1028,7 +1028,7 @@ def save(self, filename="startup-config"): filename (str, optional): Name of startup configuration file. Defaults to "startup-config". Returns: - bool: True if configuration saved succesfully. + (bool): True if configuration saved succesfully. """ command = f"copy running-config {filename}" # Changed to send_command_timing to not require a direct prompt return. @@ -1093,7 +1093,7 @@ def show(self, command, expect_string=None): expect_string (str, optional): Expected response from running command on device. Defaults to None. Returns: - str: Output from running command on device. + (str): Output from running command on device. """ self.enable() log.debug("Host %s: Successfully executed command 'show' with responses.", self.host) @@ -1123,7 +1123,7 @@ def uptime(self): """Get uptime from device. Returns: - int: Uptime in seconds. + (int): Uptime in seconds. """ if self._uptime is None: version_data = self._raw_version_data() @@ -1137,7 +1137,7 @@ def uptime_string(self): """Get uptime in format dd:hh:mm. Returns: - str: Uptime of device. + (str): Uptime of device. """ if self._uptime_string is None: version_data = self._raw_version_data() @@ -1151,7 +1151,7 @@ def hostname(self): """Get hostname of device. Returns: - str: Hostname of device. + (str): Hostname of device. """ version_data = self._raw_version_data() if self._hostname is None: @@ -1165,7 +1165,7 @@ def interfaces(self): Get list of interfaces on device. Returns: - list: List of interfaces on device. + (list): List of interfaces on device. """ if self._interfaces is None: self._interfaces = list(x["interface"] for x in self._interfaces_detailed_list()) @@ -1177,7 +1177,7 @@ def model(self): """Get the device model. Returns: - str: Device model. + (str): Device model. """ version_data = self._raw_version_data() if self._model is None: @@ -1190,7 +1190,7 @@ def os_version(self): """Get os version on device. Returns: - str: OS version on device. + (str): OS version on device. """ version_data = self._raw_version_data() if self._os_version is None: @@ -1203,7 +1203,7 @@ def serial_number(self): """Get serial number of device. Returns: - str: Serial number of device. + (str): Serial number of device. """ version_data = self._raw_version_data() if self._serial_number is None: @@ -1216,7 +1216,7 @@ def vlans(self): """Get vlan ids from device. Returns: - list: List of vlans + (list): List of vlans """ if self._vlans is None: self._vlans = self._show_vlan() diff --git a/pyntc/devices/base_device.py b/pyntc/devices/base_device.py index 0aecc6c6..a6273579 100644 --- a/pyntc/devices/base_device.py +++ b/pyntc/devices/base_device.py @@ -10,7 +10,7 @@ def fix_docs(cls): """Create docstring at runtime. Returns: - class: Returns the class passed in. + (class): Returns the class passed in. """ for name, func in vars(cls).items(): if hasattr(func, "__call__") and not func.__doc__: @@ -34,7 +34,7 @@ def __init__(self, host, username, password, device_type=None, **kwargs): # noq username (str): The username to authenticate with the device. password (str): The password to authenticate with the device. device_type (str, optional): Denotes which device type. Defaults to None. - kwargs: Additional keyword arguments that may be used by subclasses. + kwargs (dict): Additional keyword arguments that may be used by subclasses. """ self.host = host self.username = username @@ -59,7 +59,7 @@ def _image_booted(self, image_name, **vendor_specifics): volume: Required by F5Device as F5 boots into a volume. Returns: - bool: True if image is currently being used by the device, else False. + (bool): True if image is currently being used by the device, else False. """ raise NotImplementedError @@ -78,7 +78,7 @@ def boot_options(self): like system image and kickstart image. Returns: - A dictionary, e.g. {'kick': router_kick.img, 'sys': 'router_sys.img'} + (dict): A dictionary, e.g. {'kick': router_kick.img, 'sys': 'router_sys.img'} """ raise NotImplementedError @@ -217,7 +217,7 @@ def file_copy(self, src, dest=None, **kwargs): dest (str): The destination file path to be saved on remote flash. If none is supplied, the implementing class should use the basename of the source path. - kwargs: Additional keyword arguments that may be used by subclasses. + kwargs (dict): Additional keyword arguments that may be used by subclasses. Keyword Args: file_system (str): Supported only for IOS and NXOS. The file system for the @@ -237,7 +237,7 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): dest (str): The destination file path to be saved on remote the remote device. If none is supplied, the implementing class should use the basename of the source path. - kwargs: Additional keyword arguments that may be used by subclasses. + kwargs (dict): Additional keyword arguments that may be used by subclasses. Keyword Args: file_system (str): Supported only for IOS and NXOS. The file system for the @@ -245,14 +245,14 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): method is used to determine the correct file system to use. Returns: - True if the remote file exists, False if it doesn't. + (bool): True if the remote file exists, False if it doesn't. """ def install_os(self, image_name, **vendor_specifics): """Install the OS from specified image_name. Args: - image_name(str): The name of the image on the device to install. + image_name (str): The name of the image on the device to install. Keyword Args: kickstart (str): Option for ``NXOSDevice`` for devices that require a kickstart image. @@ -265,7 +265,7 @@ def install_os(self, image_name, **vendor_specifics): vendor_specifics (kwargs): Additional keyword arguments that may be used by subclasses. Returns: - True if system has been installed during function's call, False if OS has not been installed + (bool): True if system has been installed during function's call, False if OS has not been installed Raises: OSInstallError: When device finishes installation process, but the running image @@ -289,7 +289,7 @@ def reboot(self, wait_for_reload=False): """Reload a device. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. Raises: RebootTimeoutError: When the device is still unreachable after the timeout period. @@ -326,12 +326,12 @@ def set_boot_options(self, image_name, **vendor_specifics): """Set boot variables like system image and kickstart image. Args: - image_name: The main system image file name. + image_name (str): The main system image file name. Keyword Args: - kickstart: Option for ``NXOSDevice`` for devices that require a kickstart image. - volume: Option for ``F5Device`` to set which volume should have image installed. - file_system: Option for ``ASADevice`` and ``IOSDevice`` to set which directory + kickstart (str): Option for ``NXOSDevice`` for devices that require a kickstart image. + volume (str): Option for ``F5Device`` to set which volume should have image installed. + file_system (str): Option for ``ASADevice`` and ``IOSDevice`` to set which directory to use when setting the boot path. The default will use the directory returned by the ``_get_file_system()`` method. vendor_specifics (kwargs): Additional keyword arguments that may be used by subclasses. @@ -353,7 +353,7 @@ def show(self, command, raw_text=False): raw_text (bool): Whether to return raw text or structured data. Returns: - The output of the show command, which could be raw text or structured data. + (NotImplementedError): The output of the show command, which could be raw text or structured data. """ raise NotImplementedError @@ -378,7 +378,7 @@ def get_boot_options(self): """Get current boot variables like system image and kickstart image. Returns: - A dictionary, e.g. {'kick': router_kick.img, 'sys': 'router_sys.img'} + (dict): A dictionary, e.g. {'kick': router_kick.img, 'sys': 'router_sys.img'} """ warnings.warn("get_boot_options() is deprecated; use boot_options property.", DeprecationWarning) return self.boot_options diff --git a/pyntc/devices/eos_device.py b/pyntc/devices/eos_device.py index 4e9ab2b0..d3c8345b 100644 --- a/pyntc/devices/eos_device.py +++ b/pyntc/devices/eos_device.py @@ -50,8 +50,8 @@ def __init__(self, host, username, password, transport="http", port=None, timeou password (str): The password to authenticate with the device. transport (str): The protocol to communicate with the device. Defaults to http. port (int): The port to use to establish the connection. Defaults to None. - timeout(int): Timeout value used for connection with the device. Defaults to None. - kwargs: Additional keyword arguments. + timeout (int): Timeout value used for connection with the device. Defaults to None. + kwargs (dict): Additional keyword arguments. """ super().__init__(host, username, password, device_type="arista_eos_eapi") self.transport = transport @@ -176,7 +176,7 @@ def boot_options(self): """Get current running software. Returns: - dict: Key is ``sys`` with value being the image on the device. + (dict): Key is ``sys`` with value being the image on the device. """ image = self.show("show boot-config")["softwareImage"] image = image.replace("flash:/", "") @@ -221,7 +221,7 @@ def enable(self): """Ensure device is in enable mode. Returns: - None: Device prompt is set to enable mode. + (None): Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native_ssh.check_enable_mode(): @@ -238,7 +238,7 @@ def uptime(self): Get uptime of the device in seconds. Returns: - int: Uptime of the device. + (int): Uptime of the device. """ if self._uptime is None: sh_version_output = self.show("show version") @@ -253,7 +253,7 @@ def uptime_string(self): Get uptime of the device in the format of dd::hh::mm. Returns: - str: Uptime in string format. + (str): Uptime in string format. """ if self._uptime_string is None: self._uptime_string = self._uptime_to_string(self.uptime) @@ -265,7 +265,7 @@ def hostname(self): """Get hostname from device. Returns: - str: Hostname of the device. + (str): Hostname of the device. """ if self._hostname is None: sh_hostname_output = self.show("show hostname") @@ -279,7 +279,7 @@ def interfaces(self): """Get list of interfaces on device. Returns: - list: List of interfaces + (list): List of interfaces """ if self._interfaces is None: iface_detailed_list = self._interfaces_status_list() @@ -293,7 +293,7 @@ def vlans(self): """Get list of VLANS on device. Returns: - list: List of VLANS on device. + (list): List of VLANS on device. """ if self._vlans is None: vlans = EOSVlans(self) @@ -307,7 +307,7 @@ def fqdn(self): """Get fully-qualified domain name of device. Returns: - str: Fully-qualified domain name of device. + (str): Fully-qualified domain name of device. """ if self._fqdn is None: sh_hostname_output = self.show("show hostname") @@ -321,7 +321,7 @@ def model(self): """Get model of device. Returns: - str: Model of device. + (str): Model of device. """ if self._model is None: sh_version_output = self.show("show version") @@ -335,7 +335,7 @@ def os_version(self): """Get OS version on device. Returns: - str: OS version of device. + (str): OS version of device. """ if self._os_version is None: sh_version_output = self.show("show version") @@ -349,7 +349,7 @@ def serial_number(self): """Get serial number of device. Returns: - str: Serial number of device. + (str): Serial number of device. """ if self._serial_number is None: sh_version_output = self.show("show version") @@ -402,12 +402,12 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None): """Copy file to remote device if it exists. Args: - src (string): source file - dest (string, optional): Destintion file. Defaults to None. - file_system (string, optional): Describes device file system. Defaults to None. + src (str): source file + dest (str, optional): Destintion file. Defaults to None. + file_system (str, optional): Describes device file system. Defaults to None. Returns: - bool: True if remote file exists. + (bool): True if remote file exists. """ self.enable() if file_system is None: @@ -432,7 +432,7 @@ def install_os(self, image_name, **vendor_specifics): OSInstallError: Error in installing new OS. Returns: - bool: True if device OS is succesfully installed. + (bool): True if device OS is succesfully installed. """ timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): @@ -477,8 +477,8 @@ def reboot(self, wait_for_reload=False, **kwargs): Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. - kwargs: Additional keyword arguments, such as confirm. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + kwargs (dict): Additional keyword arguments, such as confirm. Raises: RebootTimeoutError: When the device is still unreachable after the timeout period. @@ -518,7 +518,7 @@ def running_config(self): """Return running config. Returns: - str: Running configuration. + (str): Running configuration. """ log.debug("Host %s: Show running config.", self.host) return self.show("show running-config", raw_text=True) @@ -527,7 +527,7 @@ def save(self, filename="startup-config"): """Show running configuration. Returns: - str: Running configuration. + (str): Running configuration. """ log.debug("Host %s: Copy running config with name %s.", self.host, filename) self.show(f"copy running-config {filename}") @@ -601,7 +601,7 @@ def startup_config(self): """Get startup configuration. Returns: - str: Startup configuration. + (str): Startup configuration. """ log.debug("Host %s: show startup-config", self.host) return self.show("show startup-config", raw_text=True) diff --git a/pyntc/devices/f5_device.py b/pyntc/devices/f5_device.py index 2304e51a..c59d8015 100644 --- a/pyntc/devices/f5_device.py +++ b/pyntc/devices/f5_device.py @@ -64,7 +64,7 @@ def _check_md5sum(self, filename, checksum): checksum (str): checksum used against image. Returns: - bool: True if md5 matches. Otherwise, false. + (bool): True if md5 matches. Otherwise, false. """ md5sum = self._file_copy_remote_md5(filename) @@ -102,7 +102,7 @@ def _get_active_volume(self): """Get name of active volume on the device. Returns: - str: Name of active volume. + (str): Name of active volume. """ volumes = self._get_volumes() for _volume in volumes: @@ -119,7 +119,7 @@ def _get_free_space(self): >>> "vg-db-sda" 30.98 GB [23.89 GB used / 7.10 GB free] Returns: - int: Number of gigabytes of free space. + (int): Number of gigabytes of free space. """ free_space = None free_space_output = self.api_handler.tm.util.bash.exec_cmd("run", utilCmdArgs='-c "vgdisplay -s --units G"') @@ -188,7 +188,7 @@ def _image_booted(self, image_name, **vendor_specifics): vendor_specifics (dict): Vendor specific arguments. Returns: - bool: True if booted volume is equal to active volume. Otherwise, false. + (bool): True if booted volume is equal to active volume. Otherwise, false. """ volume = vendor_specifics.get("volume") log.debug("Host %s: Checking if image %s has been booted.", self.host, image_name) @@ -201,7 +201,7 @@ def _image_exists(self, image_name): image_name (str): Name of image. Returns: - bool: True if image exists on device. Otherwise, false. + (bool): True if image exists on device. Otherwise, false. """ all_images_output = self.api_handler.tm.util.unix_ls.exec_cmd("run", utilCmdArgs="/shared/images") @@ -243,7 +243,7 @@ def _image_match(self, image_name, checksum): checksum (str): Expected checksum. Returns: - bool: True if expected checksum matches file checksum. Otherwise, false. + (bool): True if expected checksum matches file checksum. Otherwise, false. """ if self._image_exists(image_name): image = os.path.join("/shared/images", image_name) @@ -321,7 +321,7 @@ def _uptime_to_string(uptime): uptime (float): Uptime represented in a float. Returns: - str: Uptime in a string. + (str): Uptime in a string. """ days = uptime / (24 * 60 * 60) uptime = uptime % (24 * 60 * 60) @@ -340,7 +340,7 @@ def _volume_exists(self, volume_name): volume_name (str): Volume name. Returns: - bool: True if volume exists. Otherwise, false. + (bool): True if volume exists. Otherwise, false. """ result = self.api_handler.tm.sys.software.volumes.volume.exists(name=volume_name) @@ -355,7 +355,7 @@ def _wait_for_device_reboot(self, volume_name, timeout=600): timeout (int, optional): Timeout value. Defaults to 600. Returns: - bool: True if device boots into specified voluem successfully. Otherwise, false. + (bool): True if device boots into specified voluem successfully. Otherwise, false. """ end_time = time.time() + timeout time.sleep(60) @@ -417,7 +417,7 @@ def boot_options(self): """Get active volume. Returns: - dict: Key is ``active volume`` with value being the current active volume. + (dict): Key is ``active volume`` with value being the current active volume. """ active_volume = self._get_active_volume() @@ -455,7 +455,7 @@ def uptime(self): """Get uptime of device in seconds. Returns: - float: Uptime of device. + (float): Uptime of device. """ if self._uptime is None: self._uptime = self._get_uptime() @@ -469,7 +469,7 @@ def uptime_string(self): Get uptime of device in format dd:hh:mm:ss. Returns: - str: Uptime of device. + (str): Uptime of device. """ if self._uptime_string is None: self._uptime_string = self._uptime_to_string(self._get_uptime()) @@ -481,7 +481,7 @@ def hostname(self): """Get hostname of device. Returns: - str: Hostname. + (str): Hostname. """ if self._hostname is None: fqdn_split = self.fqdn.split(".") @@ -494,7 +494,7 @@ def interfaces(self): """Get list of images on the device. Returns: - list: List of images. + (list): List of images. """ if self._interfaces is None: self._interfaces = self._get_interfaces_list() @@ -506,7 +506,7 @@ def vlans(self): """Get list of vlans on device. Returns: - list: List of vlans. + (list): List of vlans. """ if self._vlans is None: self._vlans = self._get_vlans() @@ -518,7 +518,7 @@ def fqdn(self): """Get fully-qualified domain name. Returns: - str: Fully qualified domain name. + (str): Fully qualified domain name. """ if self._fqdn is None: settings = self.api_handler.tm.sys.global_settings.load() @@ -531,7 +531,7 @@ def model(self): """Get model of device. Returns: - str: Model of device. + (str): Model of device. """ if self._model is None: self._model = self._get_model() @@ -543,7 +543,7 @@ def os_version(self): """Get version of device. Returns: - str: Version on device. + (str): Version on device. """ if self._os_version is None: self._os_version = self._get_version() @@ -555,7 +555,7 @@ def serial_number(self): """Get serial number of device. Returns: - str: Serial number of device. + (str): Serial number of device. """ if self._serial_number is None: self._serial_number = self._get_serial_number() @@ -599,7 +599,7 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): NotImplementedError: Destination must be ``/shared/images``. Returns: - bool: True if image specified exists on device. Otherwise, false. + (bool): True if image specified exists on device. Otherwise, false. """ if dest and not dest.startswith("/shared/images"): log.error("Host %s: Support only for images - destination is always /shared/images.", self.host) @@ -625,7 +625,7 @@ def image_installed(self, image_name, volume): RuntimeError: Either image name or volume were not specified. Returns: - bool: True if file exists on volume. Otherwise, false. + (bool): True if file exists on volume. Otherwise, false. """ if not image_name or not volume: raise RuntimeError("image_name and volume must be specified") @@ -665,7 +665,7 @@ def install_os(self, image_name, **vendor_specifics): NTCFileNotFoundError: Error is image is not found on device. Returns: - bool: True if image is installed successfully. Otherwise, false. + (bool): True if image is installed successfully. Otherwise, false. """ volume = vendor_specifics.get("volume") if not self.image_installed(image_name, volume): @@ -692,7 +692,7 @@ def reboot(self, wait_for_reload=False, volume=None, **kwargs): Args: volume (str, optional): Active volume to reboot. Defaults to None. - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. kwargs (dict): Additional keyword arguments. Raises: diff --git a/pyntc/devices/ios_device.py b/pyntc/devices/ios_device.py index 77fc5d99..c5c78e22 100644 --- a/pyntc/devices/ios_device.py +++ b/pyntc/devices/ios_device.py @@ -57,7 +57,7 @@ def __init__( # nosec secret (str): The password to escalate privilege on the device. port (int): The port to use to establish the connection. Defaults to 22. confirm_active (bool): Determines if device's high availability state should be validated before leaving connection open. - kwargs: Additional arguments to pass to the Netmiko ConnectHandler. + kwargs (dict): Additional arguments to pass to the Netmiko ConnectHandler. """ super().__init__(host, username, password, device_type="cisco_ios_ssh") @@ -116,7 +116,7 @@ def _get_file_system(self): """Determine the default file system or directory for device. Returns: - str: The name of the default file system or directory for the device. + (str): The name of the default file system or directory for the device. Raises: FileSystemNotFound: When the module is unable to determine the default file system. @@ -266,7 +266,7 @@ def boot_options(self): """Get current boot image. Returns: - dict: Key ``sys`` with value being the current boot image. + (dict): Key ``sys`` with value being the current boot image. """ boot_path_regex = r"(?:BOOT variable\s+=\s+(\S+)\s*$|BOOT path-list\s+:\s*(\S+)\s*$)" try: @@ -328,11 +328,11 @@ def config(self, command, **netmiko_args): Args: command (str|list): The command or commands to send to the device. - **netmiko_args: Any argument supported by ``netmiko.ConnectHandler.send_config_set``. + **netmiko_args (dict): Any argument supported by ``netmiko.ConnectHandler.send_config_set``. Returns: - str: When ``command`` is a str, the config session input and output from sending ``command``. - list: When ``command`` is a list, the config session input and output from sending ``command``. + (str): When ``command`` is a str, the config session input and output from sending ``command``. + (list): When ``command`` is a list, the config session input and output from sending ``command``. Raises: TypeError: When sending an argument in ``**netmiko_args`` that is not supported. @@ -402,7 +402,7 @@ def confirm_is_active(self): Confirm that the device is either standalone or the active device in a high availability cluster. Returns: - bool: True when the device is considered active. + (bool): True when the device is considered active. Rasies: DeviceNotActiveError: When the device is not considered the active device. @@ -445,7 +445,7 @@ def connected(self): # noqa: D401 Get connection status of the device. Returns: - bool: True if the device is connected, else False. + (bool): True if the device is connected, else False. """ return self._connected @@ -457,7 +457,7 @@ def enable(self): """Ensure device is in enable mode. Returns: - None: Device prompt is set to enable mode. + (None): Device prompt is set to enable mode. """ # Netmiko reports enable and config mode as being enabled if not self.native.check_enable_mode(): @@ -473,7 +473,7 @@ def uptime(self): """Get uptime from device. Returns: - int: Uptime in seconds. + (int): Uptime in seconds. """ if self._uptime is None: version_data = self._raw_version_data() @@ -488,7 +488,7 @@ def uptime_string(self): """Get uptime in format dd:hh:mm. Returns: - str: Uptime of device. + (str): Uptime of device. """ if self._uptime_string is None: version_data = self._raw_version_data() @@ -502,7 +502,7 @@ def hostname(self): """Get hostname of device. Returns: - str: Hostname of device. + (str): Hostname of device. """ version_data = self._raw_version_data() if self._hostname is None: @@ -517,7 +517,7 @@ def interfaces(self): Get list of interfaces on device. Returns: - list: List of interfaces on device. + (list): List of interfaces on device. """ if self._interfaces is None: self._interfaces = list(x["intf"] for x in self._interfaces_detailed_list()) @@ -531,7 +531,7 @@ def vlans(self): Get list of VLANs on device. Returns: - list: List of VLANs on device. + (list): List of VLANs on device. """ if self._vlans is None: if self.model.startswith("WS"): @@ -547,7 +547,7 @@ def fqdn(self): """Get fully qualified domain name. Returns: - str: Fully qualified domain name or ``N/A`` if not defined. + (str): Fully qualified domain name or ``N/A`` if not defined. """ if self._fqdn is None: self._fqdn = "N/A" @@ -560,7 +560,7 @@ def model(self): """Get the device model. Returns: - str: Device model. + (str): Device model. """ version_data = self._raw_version_data() if self._model is None: @@ -574,7 +574,7 @@ def os_version(self): """Get os version on device. Returns: - str: OS version on device. + (str): OS version on device. """ version_data = self._raw_version_data() if self._os_version is None: @@ -588,7 +588,7 @@ def serial_number(self): """Get serial number of device. Returns: - str: Serial number of device. + (str): Serial number of device. """ version_data = self._raw_version_data() if self._serial_number is None: @@ -602,7 +602,7 @@ def config_register(self): """Get config register of device. Returns: - str: Config register. + (str): Config register. """ # ios-specific facts version_data = self._raw_version_data() @@ -671,7 +671,7 @@ def file_copy_remote_exists(self, src, dest=None, file_system=None): file_system (str, optional): File system to copy file to. Defaults to None. Returns: - bool: True if file copied succesfully and md5 hashes match. Otherwise, false. + (bool): True if file copied succesfully and md5 hashes match. Otherwise, false. """ self.enable() if file_system is None: @@ -698,7 +698,7 @@ def install_os(self, image_name, install_mode=False, read_timeout=2000, **vendor OSInstallError: Unable to install OS Error type Returns: - bool: False if no install is needed, true if the install completes successfully + (bool): False if no install is needed, true if the install completes successfully """ timeout = vendor_specifics.get("timeout", 3600) if not self._image_booted(image_name): @@ -759,7 +759,7 @@ def is_active(self): Determine if the current processor is the active processor. Returns: - bool: True if the processor is active or does not support HA, else False. + (bool): True if the processor is active or does not support HA, else False. Example: >>> device = IOSDevice(**connection_args) @@ -826,8 +826,8 @@ def peer_redundancy_state(self): Determine the current redundancy state of the peer processor. Returns: - str: The redundancy state of the peer processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the peer processor. + (None): When the processor does not support redundancy. Example: >>> device = IOSDevice(**connection_args) @@ -857,8 +857,8 @@ def reboot(self, wait_for_reload=False, **kwargs): Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. - kwargs: Additional arguments to pass to the Netmiko. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + kwargs (dict): Additional arguments to pass to the Netmiko. Raises: ReloadTimeoutError: When the device is still unreachable after the timeout period. @@ -890,7 +890,7 @@ def redundancy_mode(self): Get operating redundancy mode of the device. Returns: - str: The redundancy mode the device is operating in. + (str): The redundancy mode the device is operating in. If the command is not supported, then "n/a" is returned. Example: @@ -917,8 +917,8 @@ def redundancy_state(self): Determine the current redundancy state of the processor. Returns: - str: The redundancy state of the current processor. - None: When the processor does not support redundancy. + (str): The redundancy state of the current processor. + (None): When the processor does not support redundancy. Example: >>> device = IOSDevice(**connection_args) @@ -960,7 +960,7 @@ def running_config(self): """Get running configuration. Returns: - str: Output of ``show running-config``. + (str): Output of ``show running-config``. """ log.debug("Host %s: Show running config.", self.host) return self.show("show running-config") @@ -972,7 +972,7 @@ def save(self, filename="startup-config"): filename (str, optional): Name of file to save running configuration. Defaults to "startup-config". Returns: - bool: True if save is succesfull. + (bool): True if save is succesfull. """ command = f"copy running-config {filename}" # Changed to send_command_timing to not require a direct prompt return. @@ -1067,7 +1067,7 @@ def show(self, command, expect_string=None, **netmiko_args): netmiko_args (dict): Additional arguments to pass to Netmiko's send_command method. Returns: - str: Output of command. + (str): Output of command. """ self.enable() if isinstance(command, list): @@ -1088,7 +1088,7 @@ def startup_config(self): """Get startup configuration. Returns: - str: Startup configuration from device. + (str): Startup configuration from device. """ log.debug("Host %s: Successfully executed command 'show startup-config'.", self.host) return self.show("show startup-config") diff --git a/pyntc/devices/iosxewlc_device.py b/pyntc/devices/iosxewlc_device.py index 9539b1e2..8825f3e3 100644 --- a/pyntc/devices/iosxewlc_device.py +++ b/pyntc/devices/iosxewlc_device.py @@ -52,7 +52,7 @@ def install_os(self, image_name, read_timeout=2000, **vendor_specifics): OSInstallError: Unable to install OS Error type Returns: - bool: False if no install is needed, true if the install completes successfully + (bool): False if no install is needed, true if the install completes successfully """ timeout = vendor_specifics.get("timeout", 5400) if not self._image_booted(image_name): @@ -95,7 +95,7 @@ def show(self, command, expect_string=None, **netmiko_args): netmiko_args (dict): Additional arguments to pass to Netmiko's send_command method. Returns: - str: Output of command. + (str): Output of command. """ self.enable() log.debug("Host %s: Successfully executed command 'show'.", self.host) diff --git a/pyntc/devices/jnpr_device.py b/pyntc/devices/jnpr_device.py index 8effc13f..660d1c39 100644 --- a/pyntc/devices/jnpr_device.py +++ b/pyntc/devices/jnpr_device.py @@ -33,8 +33,8 @@ def __init__(self, host, username, password, *args, **kwargs): # noqa: D403 host (str): The address of the network device. username (str): The username to authenticate with the device. password (str): The password to authenticate with the device. - args: Additional positional arguments to pass to the device. - kwargs: Additional keyword arguments to pass to the device. + args (tuple): Additional positional arguments to pass to the device. + kwargs (dict): Additional keyword arguments to pass to the device. """ super().__init__(host, username, password, *args, device_type="juniper_junos_netconf", **kwargs) @@ -126,7 +126,7 @@ def boot_options(self): """Get os version on device. Returns: - str: OS version on device. + (str): OS version on device. """ return self.os_version @@ -175,7 +175,7 @@ def connected(self): """Get connection status of device. Returns: - bool: True if connection is active. Otherwise, false. + (bool): True if connection is active. Otherwise, false. """ return self.native.connected @@ -184,7 +184,7 @@ def uptime(self): """Get device uptime in seconds. Returns: - int: Device uptime in seconds. + (int): Device uptime in seconds. """ try: native_uptime_string = self.native.facts["RE0"]["up_time"] @@ -203,7 +203,7 @@ def uptime_string(self): Get device uptime in format dd:hh:mm:ss. Returns: - str: Device uptime. + (str): Device uptime. """ try: native_uptime_string = self.native.facts["RE0"]["up_time"] @@ -220,7 +220,7 @@ def hostname(self): """Get device hostname. Returns: - str: Device hostname. + (str): Device hostname. """ if self._hostname is None: self._hostname = self.native.facts.get("hostname") @@ -232,7 +232,7 @@ def interfaces(self): """Get list of interfaces. Returns: - list: List of interfaces. + (list): List of interfaces. """ if self._interfaces is None: self._interfaces = self._get_interfaces() @@ -244,7 +244,7 @@ def fqdn(self): """Get fully qualified domain name. Returns: - str: Fully qualified domain name. + (str): Fully qualified domain name. """ if self._fqdn is None: self._fqdn = self.native.facts.get("fqdn") @@ -256,7 +256,7 @@ def model(self): """Get device model. Returns: - str: Device model. + (str): Device model. """ if self._model is None: self._model = self.native.facts.get("model") @@ -268,7 +268,7 @@ def os_version(self): """Get OS version. Returns: - str: OS version. + (str): OS version. """ if self._os_version is None: self._os_version = self.native.facts.get("version") @@ -280,7 +280,7 @@ def serial_number(self): """Get serial number. Returns: - str: Serial number. + (str): Serial number. """ if self._serial_number is None: self._serial_number = self.native.facts.get("serialnumber") @@ -293,7 +293,7 @@ def file_copy(self, src, dest=None, **kwargs): Args: src (str): Name of file to be transferred. dest (str, optional): Path on device to save file. Defaults to None. - kwargs: Additional keyword arguments to pass to the `file_copy` command. + kwargs (dict): Additional keyword arguments to pass to the `file_copy` command. Raises: FileTransferError: Raised when unable to verify file was transferred succesfully. @@ -317,10 +317,10 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): Args: src (str): Source of local file. dest (str, optional): Path of file on device. Defaults to None. - kwargs: Additional keyword arguments to pass to the `file_copy` command. + kwargs (dict): Additional keyword arguments to pass to the `file_copy` command. Returns: - bool: True if hashes of the file match. Otherwise, false. + (bool): True if hashes of the file match. Otherwise, false. """ if dest is None: dest = os.path.basename(src) @@ -353,8 +353,8 @@ def reboot(self, wait_for_reload=False, **kwargs): Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. - kwargs: Additional keyword arguments to pass to the `reboot` command. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + kwargs (dict): Additional keyword arguments to pass to the `reboot` command. Example: >>> device = JunosDevice(**connection_args) @@ -395,7 +395,7 @@ def running_config(self): """Get running configuration. Returns: - str: Running configuration. + (str): Running configuration. """ return self.show("show config") @@ -409,7 +409,7 @@ def save(self, filename=None): filename (str, optional): Filename to save current configuration. Defaults to None. Returns: - bool: True if new file created for save file. Otherwise, just returns if save is to default name. + (bool): True if new file created for save file. Otherwise, just returns if save is to default name. """ if filename is None: self.cu.commit() @@ -467,6 +467,6 @@ def startup_config(self): """Get startup configuration. Returns: - str: Startup configuration. + (str): Startup configuration. """ return self.show("show config") diff --git a/pyntc/devices/nxos_device.py b/pyntc/devices/nxos_device.py index e96ac640..1517c28d 100644 --- a/pyntc/devices/nxos_device.py +++ b/pyntc/devices/nxos_device.py @@ -39,7 +39,7 @@ def __init__(self, host, username, password, transport="http", timeout=30, port= timeout (int, optional): Timeout in seconds. Defaults to 30. port (int, optional): Port used to connect to device. Defaults to None. verify (bool, optional): SSL verification. - kwargs: Left for compatibility with other tools, for instance nautobot-inventory may pass additional kwargs. + kwargs (dict): Left for compatibility with other tools, for instance nautobot-inventory may pass additional kwargs. """ super().__init__(host, username, password, device_type="cisco_nxos_nxapi") @@ -89,7 +89,7 @@ def boot_options(self): """Get current boot variables. Returns: - dict: e.g . {"kick": "router_kick.img", "sys": "router_sys.img"} + (dict): e.g . {"kick": "router_kick.img", "sys": "router_sys.img"} """ boot_options = self.native.get_boot_options() log.debug("Host %s: the boot options are %s", self.host, boot_options) @@ -137,7 +137,7 @@ def uptime(self): """Get uptime of the device in seconds. Returns: - int: Uptime of the device in seconds. + (int): Uptime of the device in seconds. """ if self._uptime is None: self._uptime = self.native.facts.get("uptime") @@ -150,7 +150,7 @@ def uptime_string(self): """Get uptime in format dd:hh:mm. Returns: - str: Uptime of device. + (str): Uptime of device. """ if self._uptime_string is None: self._uptime_string = self.native.facts.get("uptime_string") @@ -162,7 +162,7 @@ def hostname(self): """Get hostname of the device. Returns: - str: Hostname of the device. + (str): Hostname of the device. """ if self._hostname is None: self._hostname = self.native.facts.get("hostname") @@ -175,7 +175,7 @@ def interfaces(self): """Get list of interfaces. Returns: - list: List of interfaces. + (list): List of interfaces. """ if self._interfaces is None: self._interfaces = self.native.facts.get("interfaces") @@ -188,7 +188,7 @@ def vlans(self): """Get list of vlans. Returns: - list: List of vlans on the device. + (list): List of vlans on the device. """ if self._vlans is None: self._vlans = self.native.facts.get("vlans") @@ -201,7 +201,7 @@ def fqdn(self): """Get fully qualified domain name. Returns: - str: Fully qualified domain name. + (str): Fully qualified domain name. """ if self._fqdn is None: self._fqdn = self.native.facts.get("fqdn") @@ -214,7 +214,7 @@ def model(self): """Get device model. Returns: - str: Model of device. + (str): Model of device. """ if self._model is None: self._model = self.native.facts.get("model") @@ -227,7 +227,7 @@ def os_version(self): """Get device version. Returns: - str: Device version. + (str): Device version. """ if self._os_version is None: self._os_version = self.native.facts.get("os_version") @@ -240,7 +240,7 @@ def serial_number(self): """Get device serial number. Returns: - str: Device serial number. + (str): Device serial number. """ if self._serial_number is None: self._serial_number = self.native.facts.get("serial_number") @@ -289,7 +289,7 @@ def file_copy_remote_exists(self, src, dest=None, file_system="bootflash:"): file_system (str, optional): The file system for the remote file. Defaults to "bootflash:". Returns: - bool: True if the remote file exists. Otherwise, false. + (bool): True if the remote file exists. Otherwise, false. """ dest = dest or os.path.basename(src) log.debug( @@ -311,7 +311,7 @@ def install_os(self, image_name, **vendor_specifics): OSInstallError: Error if boot option is not set to new image. Returns: - bool: True if new image is boot option on device. Otherwise, false. + (bool): True if new image is boot option on device. Otherwise, false. """ self.native.show("terminal dont-ask") timeout = vendor_specifics.get("timeout", 3600) @@ -338,8 +338,8 @@ def reboot(self, wait_for_reload=False, **kwargs): Reload the controller or controller pair. Args: - wait_for_reload: Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. - kwargs: Additional arguments to pass to reboot method. + wait_for_reload (bool): Whether or not reboot method should also run _wait_for_device_reboot(). Defaults to False. + kwargs (dict): Additional arguments to pass to reboot method. Raises: RebootTimerError: When the device is still unreachable after the timeout period. @@ -385,7 +385,7 @@ def running_config(self): """Get running configuration of device. Returns: - str: Running configuration of device. + (str): Running configuration of device. """ log.debug("Host %s: Show running config.", self.host) return self.native.running_config @@ -397,7 +397,7 @@ def save(self, filename="startup-config"): filename (str, optional): Filename to save running configuration to. Defaults to "startup-config". Returns: - bool: True if configuration is saved. + (bool): True if configuration is saved. """ log.debug("Host %s: Copy running config with name %s.", self.host, filename) return self.native.save(filename=filename) @@ -456,7 +456,7 @@ def show(self, command, raw_text=False): CommandError: Error message stating which command failed. Returns: - str: Results of the command ran. + (str): Results of the command ran. """ log.debug("Host %s: Successfully executed command 'show' with responses.", self.host) if isinstance(command, list): @@ -478,6 +478,6 @@ def startup_config(self): """Get startup configuration. Returns: - str: Startup configuration. + (str): Startup configuration. """ return self.show("show startup-config", raw_text=True) diff --git a/pyntc/devices/system_features/vlans/base_vlans.py b/pyntc/devices/system_features/vlans/base_vlans.py index da31f5b4..19602cae 100644 --- a/pyntc/devices/system_features/vlans/base_vlans.py +++ b/pyntc/devices/system_features/vlans/base_vlans.py @@ -19,11 +19,7 @@ class BaseVlans(BaseFeature): class VlanNotInRangeError(NTCError): - """Vlan error. - - Args: - NTCError (str): Vlan range error. - """ + """Vlan error.""" def __init__(self, lower, upper): """Exception for vlan range validation. diff --git a/pyntc/log.py b/pyntc/log.py index 74d8b968..15e31f53 100644 --- a/pyntc/log.py +++ b/pyntc/log.py @@ -21,7 +21,7 @@ def get_log(name=None): name (str, optional): Sublogger name. Defaults to None. Returns: - logger: Return a logger instance in the :data:`APP` namespace. + (logger): Return a logger instance in the :data:`APP` namespace. """ logger_name = f"{APP}.{name}" if name else APP # file handler @@ -39,7 +39,7 @@ def init(**kwargs): directly to the :func:`logging.basicConfig` call in turn. Args: - **kwargs: Arguments to pass for logging configuration + **kwargs (dict): Arguments to pass for logging configuration """ @@ -64,7 +64,7 @@ def logger(level): level (str): defines the log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) Returns: - string: Returns logger. type of string. + (str): Returns logger. type of string. """ return getattr(get_log(), level) diff --git a/pyntc/utils/converters.py b/pyntc/utils/converters.py index 634accb0..61fd2b55 100644 --- a/pyntc/utils/converters.py +++ b/pyntc/utils/converters.py @@ -9,13 +9,13 @@ def convert_dict_by_key(original, key_map, fill_in=False, whitelist=[], blacklis key_map (dict): Key map to use to convert dictionary. fill_in (dict): Whether the returned dictionary should contain keys and values from the original dictionary if not specified in the key map. - whitelist: If fill_in is True, and whitelist isn't empty, only fill in the keys + whitelist (list): If fill_in is True, and whitelist isn't empty, only fill in the keys in the whitelist in the returned dictionary. - blacklist: If fill_in is True, and blacklist isn't empty, fill in with all keys from + blacklist (list): If fill_in is True, and blacklist isn't empty, fill in with all keys from the original dictionary besides those in the blacklist. Returns: - A converted dictionary through the key map. + (dict): A converted dictionary through the key map. """ converted = {} for converted_key in key_map: @@ -49,13 +49,13 @@ def convert_list_by_key(original_list, key_map, fill_in=False, whitelist=[], bla key_map (dict): Key map to use to convert list. fill_in (dict): Whether the returned list should contain keys and values from the original dictionary if not specified in the key map. - whitelist: If fill_in is True, and whitelist isn't empty, only fill in the keys + whitelist (list): If fill_in is True, and whitelist isn't empty, only fill in the keys in the whitelist in the returned dictionary. - blacklist: If fill_in is True, and blacklist isn't empty, fill in with all keys from + blacklist (list): If fill_in is True, and blacklist isn't empty, fill in with all keys from the original dictionary besides those in the blacklist. Returns: - list: A converted list. + (list): A converted list. """ converted_list = [] for original in list(original_list): diff --git a/pyntc/utils/templates/__init__.py b/pyntc/utils/templates/__init__.py index eac6c6e1..1029a1d5 100644 --- a/pyntc/utils/templates/__init__.py +++ b/pyntc/utils/templates/__init__.py @@ -15,7 +15,7 @@ def get_structured_data(template_name, rawtxt): rawtxt (str): Raw output from device. Returns: - list: A dict per entry returned by TextFSM. + (list): A dict per entry returned by TextFSM. """ template_file = get_template(template_name) with open(template_file, encoding="utf-8") as template: @@ -37,7 +37,7 @@ def get_template(template_name): template_name (str): Name of the template. Returns: - str: Path to the template. + (str): Path to the template. """ template_dir = get_template_dir() return os.path.join(template_dir, template_name) @@ -47,7 +47,7 @@ def get_template_dir(): """Get directory of NTC_TEMPLATE os environment. Returns: - str: Path to NTC_TEMPLATES environment variable if set. Otherwise, path to this file. + (str): Path to NTC_TEMPLATES environment variable if set. Otherwise, path to this file. """ try: return os.environ[TEMPLATE_PATH_ENV_VAR] diff --git a/pyproject.toml b/pyproject.toml index 5cccd863..e15a2a7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,13 +43,7 @@ pyntc = 'pyntc.cli:main' requests_mock = "*" pytest = "*" mock = "*" -mkdocs = "^1.6.0" -mkdocs-material = "9.5.32" -mkdocstrings = "0.25.2" -mkdocstrings-python = "1.10.8" griffe = "1.1.1" -markdown-data-tables = "^1.0.0" -mkdocs-version-annotations = "^1.0.0" pyyaml = "^6.0.1" pylint = "^3.1.0" yamllint = "^1.35.1" @@ -58,6 +52,29 @@ toml = "^0.10.2" attrs = "^23.2.0" towncrier = "^24.8.0" ruff = "*" +Markdown = "*" +# Rendering docs to HTML +mkdocs = "1.6.1" +# Embedding YAML files into Markdown documents as tables +markdown-data-tables = "1.0.0" +# Render custom markdown for version added/changed/remove notes +markdown-version-annotations = "1.0.1" +# Automatically generate some files as part of mkdocs build +mkdocs-gen-files = "0.5.0" +# Image lightboxing in mkdocs +mkdocs-glightbox = "0.4.0" +# Use Jinja2 templating in docs - see settings.md +mkdocs-macros-plugin = "1.3.7" +# Material for mkdocs theme +mkdocs-material = "9.6.15" +# Handle docs redirections +mkdocs-redirects = "1.2.2" +# Automatically handle index pages for docs sections +mkdocs-section-index = "0.3.10" +# Automatic documentation from sources, for MkDocs +mkdocstrings = "0.27.0" +# Python-specific extension to mkdocstrings +mkdocstrings-python = "1.13.0" [tool.pyntc] string_required = "some string" diff --git a/tests/unit/test_basic.py b/tests/unit/test_basic.py new file mode 100644 index 00000000..bbe72654 --- /dev/null +++ b/tests/unit/test_basic.py @@ -0,0 +1,48 @@ +"""Basic tests that do not require Django.""" + +import os +import re +import unittest + +import toml + + +class TestDocsPackaging(unittest.TestCase): + """Test Version in doc requirements is the same pyproject.""" + + def test_version(self): + """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_path = os.path.join(parent_path, "pyproject.toml") + poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] + with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: + requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] + for pkg in requirements: + package_name = pkg + if len(pkg.split("==")) == 2: # noqa: PLR2004 + package_name, version = pkg.split("==") + else: + version = "*" + self.assertEqual(poetry_details[package_name], version) + + +class TestDocsReleaseNotes(unittest.TestCase): + """Test that mkdocs has the release notes for the current version.""" + + def test_version_file_found(self): + """Verify that if the current version has no letters, which would see in alpha or beta has an associated release note file.""" + parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + poetry_path = os.path.join(parent_path, "pyproject.toml") + project_version = toml.load(poetry_path)["tool"]["poetry"]["version"] + + docs_path = os.path.join(parent_path, "docs") + release_notes_files = [file for file in os.listdir(f"{docs_path}/admin/release_notes/") if file.endswith(".md")] + version_pattern = re.compile(r"^(\d+)\.(\d+)\.\d+$") + + match = version_pattern.match(project_version) + # If there is no match, then it is likely an alpha or beta version and we can skip this test. + if match: + major, minor = match.groups() + version_str = f"version_{major}.{minor}.md" + if version_str not in release_notes_files: + self.fail(f"Release note file for version {version_str} not found in release notes folder.") From db3b68abfdf878b8206dcfa8cfa50beefb4838c3 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Fri, 27 Feb 2026 10:43:34 -0600 Subject: [PATCH 07/10] Add file_pull option to Cisco IOS devices (#345) * Add file_pull option to Cisco IOS devices * Add docs, ruff and change fragment. * Pylint fixes Ignore the import position as Ruff is handling that. * Address feedback * Fix pylint commands The find command was too greedy and capturing files ignore by the pylint config in pyproject.toml. This allows pylint to use the pyproject.tom. * Rewrite to make the logic for checking files part of the Device. Not all Devices use netmiko, these changes allow other devices to also implement fix exist validation. * Update ios_device.py * Ruff * Apply suggestions from code review Co-authored-by: Gary Snider <75227981+gsnider2195@users.noreply.github.com> * Apply suggestions from code review. --------- Co-authored-by: Gary Snider <75227981+gsnider2195@users.noreply.github.com> --- changes/345.added | 1 + docs/user/lib_getting_started.md | 29 +++ pyntc/devices/base_device.py | 119 +++++++++++- pyntc/devices/ios_device.py | 182 +++++++++++++++++- pyntc/utils/models.py | 75 ++++++++ tasks.py | 2 +- tests/unit/test_devices/test_ios_device.py | 203 +++++++++++++++++++-- 7 files changed, 593 insertions(+), 18 deletions(-) create mode 100644 changes/345.added create mode 100644 pyntc/utils/models.py diff --git a/changes/345.added b/changes/345.added new file mode 100644 index 00000000..f7a7e976 --- /dev/null +++ b/changes/345.added @@ -0,0 +1 @@ +Added the ability to download files from within a Cisco IOS device. diff --git a/docs/user/lib_getting_started.md b/docs/user/lib_getting_started.md index f164d661..bd530fdd 100644 --- a/docs/user/lib_getting_started.md +++ b/docs/user/lib_getting_started.md @@ -250,6 +250,35 @@ interface GigabitEthernet1 >>> ``` +#### Remote File Copy (Download to Device) + +Some devices support copying files directly from a URL to the device. This is useful for larger files like OS images. To do this, you need to use the `FileCopyModel` data model to specify the source file information and then pass that to the `remote_file_copy` method. Currently only supported on Cisco IOS devices. Tested with ftp, http, https, sftp, and tftp urls. + +- `remote_file_copy` method + +```python +from pyntc.utils.models import FileCopyModel + +>>> source_file = FileCopyModel( +... download_url='sftp://example.com/newconfig.cfg', +... checksum='abc123def456', +... hashing_algorithm='md5', +... file_name='newconfig.cfg', + vrf='Mgmt-vrf' +... ) +>>> for device in devices: +... device.remote_file_copy(source_file) +... +>>> +``` + +Before using this feature you may need to configure a client on the device. For instance, on a Cisco IOS device you would need to set the source interface for the ip http client when using http or https urls. You can do this with the `config` method: + +```python +>>> csr1.config('ip http client source-interface GigabitEthernet1') +>>> +``` + ### Save Configs - `save` method diff --git a/pyntc/devices/base_device.py b/pyntc/devices/base_device.py index a6273579..abe02237 100644 --- a/pyntc/devices/base_device.py +++ b/pyntc/devices/base_device.py @@ -1,9 +1,11 @@ """The module contains the base class that all device classes must inherit from.""" +import hashlib import importlib import warnings from pyntc.errors import FeatureNotFoundError, NTCError +from pyntc.utils.models import FileCopyModel def fix_docs(cls): @@ -221,7 +223,7 @@ def file_copy(self, src, dest=None, **kwargs): Keyword Args: file_system (str): Supported only for IOS and NXOS. The file system for the - remote fle. If no file_system is provided, then the ``get_file_system`` + remote file. If no file_system is provided, then the ``get_file_system`` method is used to determine the correct file system to use. """ raise NotImplementedError @@ -241,13 +243,126 @@ def file_copy_remote_exists(self, src, dest=None, **kwargs): Keyword Args: file_system (str): Supported only for IOS and NXOS. The file system for the - remote fle. If no file_system is provided, then the ``get_file_system`` + remote file. If no file_system is provided, then the ``get_file_system`` method is used to determine the correct file system to use. Returns: (bool): True if the remote file exists, False if it doesn't. """ + def check_file_exists(self, filename, **kwargs): + """Check if a remote file exists by filename. + + Args: + filename (str): The name of the file to check for on the remote device. + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (bool): True if the remote file exists, False if it doesn't. + """ + raise NotImplementedError + + def get_remote_checksum(self, filename, hashing_algorithm="md5", **kwargs): + """Get the checksum of a remote file. + + Args: + filename (str): The name of the file to check for on the remote device. + hashing_algorithm (str): The hashing algorithm to use (default: "md5"). + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (str): The checksum of the remote file. + """ + raise NotImplementedError + + @staticmethod + def get_local_checksum(filepath, hashing_algorithm="md5", add_newline=False): + """Get the checksum of a local file using a specified algorithm. + + Args: + filepath (str): The path to the local file. + hashing_algorithm (str): The hashing algorithm to use (e.g., "md5", "sha256"). + add_newline (bool): Whether to append a newline before final hashing (Some devices may require this). + + Returns: + (str): The hex digest of the file. + """ + # Initialize the hash object dynamically + file_hash = hashlib.new(hashing_algorithm.lower()) + + with open(filepath, "rb") as f: + # Read in chunks to handle large firmware files without RAM spikes + for chunk in iter(lambda: f.read(4096), b""): + file_hash.update(chunk) + + if add_newline: + file_hash.update(b"\n") + + return file_hash.hexdigest() + + def compare_file_checksum(self, checksum, filename, hashing_algorithm="md5", **kwargs): + """Compare the checksum of a local file with a remote file. + + Args: + checksum (str): The checksum of the file. + filename (str): The name of the file to check for on the remote device. + hashing_algorithm (str): The hashing algorithm to use (default: "md5"). + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (bool): True if the checksums match, False otherwise. + """ + return checksum == self.get_remote_checksum(filename, hashing_algorithm, **kwargs) + + def remote_file_copy(self, src: FileCopyModel = None, dest=None, **kwargs): + """Copy a file to a remote device. + + Args: + src (FileCopyModel): The source file model. + dest (str): The destination file path on the remote device. + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + """ + raise NotImplementedError + + def verify_file(self, checksum, filename, hashing_algorithm="md5", **kwargs): + """Verify a file on the remote device by confirming the file exists and validate the checksum. + + Args: + checksum (str): The checksum of the file. + filename (str): The name of the file to check for on the remote device. + hashing_algorithm (str): The hashing algorithm to use (default: "md5"). + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (bool): True if the file is verified successfully, False otherwise. + """ + raise NotImplementedError + def install_os(self, image_name, **vendor_specifics): """Install the OS from specified image_name. diff --git a/pyntc/devices/ios_device.py b/pyntc/devices/ios_device.py index c5c78e22..f529ff85 100644 --- a/pyntc/devices/ios_device.py +++ b/pyntc/devices/ios_device.py @@ -22,6 +22,7 @@ SocketClosedError, ) from pyntc.utils import get_structured_data +from pyntc.utils.models import FileCopyModel BASIC_FACTS_KM = {"model": "hardware", "os_version": "version", "serial_number": "serial", "hostname": "hostname"} RE_SHOW_REDUNDANCY = re.compile( @@ -105,6 +106,7 @@ def _enter_config(self): log.debug("Host %s: Device entered config mode.", self.host) def _file_copy_instance(self, src, dest=None, file_system="flash:"): + """Create a FileTransfer instance for copying a file to the device.""" if dest is None: dest = os.path.basename(src) @@ -611,6 +613,101 @@ def config_register(self): log.debug("Host %s: Config register %s", self.host, self._config_register) return self._config_register + def get_remote_checksum(self, filename, hashing_algorithm="md5", file_system=None): + """Get the checksum of a remote file. + + Args: + filename (str): The name of the file to check for on the remote device. + hashing_algorithm (str): The hashing algorithm to use. Valid choices are "md5" and "sha512" (default: "md5"). + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (str): The checksum of the remote file. + + Raises: + ValueError: If an unsupported hashing algorithm is provided. + CommandError: If there is an error in executing the command to get the remote checksum. + """ + if hashing_algorithm not in {"md5", "sha512"}: + raise ValueError("hashing_algorithm must be either 'md5' or 'sha512' for Cisco IOS devices.") + if file_system is None: + file_system = self._get_file_system() + cmd = f"verify /{hashing_algorithm} {file_system}/{filename}" + result = self.native.send_command_timing(cmd, read_timeout=300) + + patterns = [r"=\s+(\S+)", r"^([a-fA-F0-9]+)$"] + for pattern in patterns: + if match := re.search(pattern, result): + log.debug( + "Host %s: Remote checksum for file %s with hashing algorithm %s is %s.", + self.host, + filename, + hashing_algorithm, + match[1], + ) + return match[1] + log.error( + "Host %s: Unable to get remote checksum for file %s with hashing algorithm %s", + self.host, + filename, + hashing_algorithm, + ) + raise CommandError( + cmd, f"Unable to get remote checksum for file {filename} with hashing algorithm {hashing_algorithm}" + ) + + def check_file_exists(self, filename, file_system=None): + """Check if a remote file exists by filename. + + Args: + filename (str): The name of the file to check for on the remote device. + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (bool): True if the remote file exists, False if it doesn't. + + Raises: + CommandError: If there is an error in executing the command to check if the file exists. + """ + cmd = f"dir {file_system or self._get_file_system()}/{filename}" + result = self.native.send_command(cmd, read_timeout=30) + log.debug( + "Host %s: Checking if file %s exists on remote with command '%s' and result: %s", + self.host, + filename, + cmd, + result, + ) + if re.search(r"No such file|No files found|Path does not exist|Error opening", result): + log.debug("Host %s: File %s does not exist on remote.", self.host, filename) + return False + if re.search(rf"Directory of .*{filename}", result): + log.debug("Host %s: File %s exists on remote.", self.host, filename) + return True + raise CommandError(cmd, f"Unable to determine if file {filename} exists on remote: {result}") + + def verify_file(self, checksum, filename, hashing_algorithm="md5", file_system=None): + """Verify a file on the remote device by and validate the checksums. + + Args: + checksum (str): The checksum of the file. + filename (str): The name of the file to check for on the remote device. + hashing_algorithm (str): The hashing algorithm to use (default: "md5"). + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Returns: + (bool): True if the file is verified successfully, False otherwise. + """ + return self.check_file_exists(filename, file_system=file_system) and self.compare_file_checksum( + checksum, filename, hashing_algorithm, file_system=file_system + ) + def file_copy(self, src, dest=None, file_system=None): """Copy file to device. @@ -628,7 +725,11 @@ def file_copy(self, src, dest=None, file_system=None): if file_system is None: file_system = self._get_file_system() - if not self.file_copy_remote_exists(src, dest, file_system): + dest = dest or os.path.basename(src) + local_checksum = self.get_local_checksum(src) + log.debug("Host %s: Local checksum for file %s is %s.", self.host, src, local_checksum) + + if not self.verify_file(local_checksum, dest, file_system=file_system): file_copy = self._file_copy_instance(src, dest, file_system=file_system) # if not self.fc.verify_space_available(): # raise FileTransferError('Not enough space available.') @@ -642,7 +743,7 @@ def file_copy(self, src, dest=None, file_system=None): # compare hashes if not file_copy.compare_md5(): log.error("Host %s: Socket closed error %s", self.host, error) - raise SocketClosedError(message=error) + raise SocketClosedError(message=error) from error log.error("Host %s: OS error %s", self.host, error) except: # noqa E722 log.error("Host %s: File transfer error %s", self.host, FileTransferError.default_message) @@ -653,7 +754,7 @@ def file_copy(self, src, dest=None, file_system=None): # Ensure connection to device is still open after long transfers self.open() - if not self.file_copy_remote_exists(src, dest, file_system): + if not self.verify_file(local_checksum, dest, file_system=file_system): log.error( "Host %s: Attempted file copy, but could not validate file existed after transfer %s", self.host, @@ -661,9 +762,82 @@ def file_copy(self, src, dest=None, file_system=None): ) raise FileTransferError + def remote_file_copy(self, src: FileCopyModel, dest=None, file_system=None, **kwargs): + """Copy a file to a remote device. + + Args: + src (FileCopyModel): The source file model. + dest (str): The destination file path on the remote device. + kwargs (dict): Additional keyword arguments that may be used by subclasses. + + Keyword Args: + file_system (str): Supported only for IOS and NXOS. The file system for the + remote file. If no file_system is provided, then the ``get_file_system`` + method is used to determine the correct file system to use. + + Raises: + TypeError: If src is not an instance of FileCopyModel. + FileTransferError: If there is an error during file transfer or if the file cannot be verified after transfer. + """ + if not isinstance(src, FileCopyModel): + raise TypeError("src must be an instance of FileCopyModel") + if file_system is None: + file_system = self._get_file_system() + if dest is None: + dest = src.file_name + if not self.verify_file(src.checksum, dest, hashing_algorithm=src.hashing_algorithm, file_system=file_system): + current_prompt = self.native.find_prompt() + + # Define prompt mapping for expected prompts during file copy + prompt_answers = { + r"Password": src.token, + r"Source username": src.username, + r"yes/no|Are you sure you want to continue connecting": "yes", + r"(confirm|Address or name of remote host|Source filename|Destination filename)": "", # Press Enter + } + keys = list(prompt_answers.keys()) + [re.escape(current_prompt)] + expect_regex = f"({'|'.join(keys)})" + + command = f"copy {src.clean_url} {file_system}{dest}" + if src.vrf and src.scheme not in {"http", "https"}: + command = f"{command} vrf {src.vrf}" + + # _send_command currently checks for % and raises an error, but during the file copy + # there may be a % warning that does not indicate a failure so we will use send_command directly. + output = self.native.send_command(command, expect_string=expect_regex, read_timeout=src.timeout) + + while current_prompt not in output: + # Check for success message in output to break loop and avoid waiting for next prompt + if re.search(r"Copy complete|bytes copied in|File transfer successful", output, re.IGNORECASE): + log.info( + "Host %s: File %s transferred successfully with output: %s", self.host, src.file_name, output + ) + break + # Check for errors explicitly to avoid infinite loops on failure + if re.search(r"(Error|Invalid|Failed|Aborted|denied)", output, re.IGNORECASE): + log.error("Host %s: File transfer error %s", self.host, FileTransferError.default_message) + raise FileTransferError + for prompt, answer in prompt_answers.items(): + if re.search(prompt, output, re.IGNORECASE): + is_password = "Password" in prompt + output = self.native.send_command( + answer, expect_string=expect_regex, read_timeout=src.timeout, cmd_verify=not is_password + ) + break # Exit the for loop and check the new output for the next prompt + + if not self.verify_file( + src.checksum, dest, hashing_algorithm=src.hashing_algorithm, file_system=file_system + ): + log.error( + "Host %s: Attempted remote file copy, but could not validate file existed after transfer %s", + self.host, + FileTransferError.default_message, + ) + raise FileTransferError + # TODO: Make this an internal method since exposing file_copy should be sufficient def file_copy_remote_exists(self, src, dest=None, file_system=None): - """Copy file to device. + """Check if file exists on remote device. Args: src (str): Source of file. diff --git a/pyntc/utils/models.py b/pyntc/utils/models.py new file mode 100644 index 00000000..3d047a81 --- /dev/null +++ b/pyntc/utils/models.py @@ -0,0 +1,75 @@ +"""Data Models for Pyntc.""" + +from dataclasses import asdict, dataclass, field +from typing import Optional +from urllib.parse import urlparse + +# Use Hashing algorithms from Nautobot's supported list. +HASHING_ALGORITHMS = {"md5", "sha1", "sha224", "sha384", "sha256", "sha512", "sha3", "blake2", "blake3"} + + +@dataclass +class FileCopyModel: + """Data class to represent the specification for pulling a file from a URL to a network device. + + Args: + download_url (str): The URL to download the file from. Can include credentials, but it's recommended to use the username and token fields instead for security reasons. + checksum (str): The expected checksum of the file. + file_name (str): The name of the file to be saved on the device. + hashing_algorithm (str, optional): The hashing algorithm to use for checksum verification. Defaults to "md5". + timeout (int, optional): The timeout for the download operation in seconds. Defaults to 900. + file_size (int, optional): The expected size of the file in bytes. Optional but can be used for an additional layer of verification. + username (str, optional): The username for authentication if required by the URL. Optional if credentials are included in the URL. + token (str, optional): The password or token for authentication if required by the URL. Optional if credentials are included in the URL. + vrf (str, optional): The VRF to use for the download if the device supports VRFs. Optional. + ftp_passive (bool, optional): Whether to use passive mode for FTP downloads. Defaults to True. + """ + + download_url: str + checksum: str + file_name: str + hashing_algorithm: str = "md5" + timeout: int = 900 # Timeout for the download operation in seconds + file_size: Optional[int] = None # Size in bytes + username: Optional[str] = None + token: Optional[str] = None # Password/Token + vrf: Optional[str] = None + ftp_passive: bool = True + + # This field is calculated, so we don't pass it in the constructor + clean_url: str = field(init=False) + scheme: str = field(init=False) + + def __post_init__(self): + """Validate the input and prepare the clean URL after initialization.""" + # 1. Validate the hashing algorithm choice + if self.hashing_algorithm.lower() not in HASHING_ALGORITHMS: + raise ValueError(f"Unsupported algorithm. Choose from: {HASHING_ALGORITHMS}") + + # Parse the url to extract components + parsed = urlparse(self.download_url) + + # Extract username/password from URL if not already provided as arguments + if parsed.username and not self.username: + self.username = parsed.username + if parsed.password and not self.token: + self.token = parsed.password + + # 3. Create the 'clean_url' (URL without the credentials) + # This is what you actually send to the device if using ip http client + port = f":{parsed.port}" if parsed.port else "" + self.clean_url = f"{parsed.scheme}://{parsed.hostname}{port}{parsed.path}" + self.scheme = parsed.scheme + + # Handle query params if they exist (though we're avoiding '?' for Cisco) + if parsed.query: + self.clean_url += f"?{parsed.query}" + + @classmethod + def from_dict(cls, data: dict): + """Allows users to just pass a dictionary if they prefer.""" + return cls(**data) + + def to_dict(self): + """Useful for logging or passing to other Nornir tasks.""" + return asdict(self) diff --git a/tasks.py b/tasks.py index 515dda44..a9624a7c 100644 --- a/tasks.py +++ b/tasks.py @@ -189,7 +189,7 @@ def pylint(context): Args: context (obj): Used to run specific commands """ - exec_cmd = 'find . -name "*.py" | grep -vE "tests/unit" | xargs pylint' + exec_cmd = "pylint --verbose pyntc" run_command(context, exec_cmd) diff --git a/tests/unit/test_devices/test_ios_device.py b/tests/unit/test_devices/test_ios_device.py index f466c2be..5cdfc4d5 100644 --- a/tests/unit/test_devices/test_ios_device.py +++ b/tests/unit/test_devices/test_ios_device.py @@ -8,6 +8,7 @@ from pyntc.devices import IOSDevice from pyntc.devices import ios_device as ios_module from pyntc.devices.base_device import RollbackError +from pyntc.utils.models import FileCopyModel from .device_mocks.ios import send_command, send_command_expect @@ -137,14 +138,17 @@ def test_file_copy_remote_exists_not(self, mock_ft): self.assertFalse(result) + @mock.patch.object(IOSDevice, "get_local_checksum") + @mock.patch.object(IOSDevice, "verify_file") @mock.patch("pyntc.devices.ios_device.FileTransfer", autospec=True) @mock.patch.object(IOSDevice, "open") - def test_file_copy(self, mock_open, mock_ft): + def test_file_copy(self, mock_open, mock_ft, mock_verify_file, mock_get_local_checksum): self.device.native.send_command.side_effect = None self.device.native.send_command.return_value = "flash: /dev/null" mock_ft_instance = mock_ft.return_value - mock_ft_instance.check_file_exists.side_effect = [False, True] + mock_verify_file.side_effect = [False, True] + mock_get_local_checksum.return_value = "dummy_checksum" self.device.file_copy("path/to/source_file") mock_ft.assert_called_with(self.device.native, "path/to/source_file", "source_file", file_system="flash:") @@ -153,14 +157,16 @@ def test_file_copy(self, mock_open, mock_ft): mock_ft_instance.transfer_file.assert_any_call() mock_open.assert_called_once() + @mock.patch.object(IOSDevice, "get_local_checksum") + @mock.patch.object(IOSDevice, "verify_file") @mock.patch("pyntc.devices.ios_device.FileTransfer", autospec=True) @mock.patch.object(IOSDevice, "open") - def test_file_copy_different_dest(self, mock_open, mock_ft): + def test_file_copy_different_dest(self, mock_open, mock_ft, mock_verify_file, mock_get_local_checksum): self.device.native.send_command_timing.side_effect = None self.device.native.send_command.return_value = "flash: /dev/null" mock_ft_instance = mock_ft.return_value - mock_ft_instance.check_file_exists.side_effect = [False, True] + mock_verify_file.side_effect = [False, True] self.device.file_copy("source_file", "dest_file") mock_ft.assert_called_with(self.device.native, "source_file", "dest_file", file_system="flash:") @@ -169,29 +175,33 @@ def test_file_copy_different_dest(self, mock_open, mock_ft): mock_ft_instance.transfer_file.assert_any_call() mock_open.assert_called_once() + @mock.patch.object(IOSDevice, "get_local_checksum") + @mock.patch.object(IOSDevice, "verify_file") @mock.patch("pyntc.devices.ios_device.FileTransfer", autospec=True) @mock.patch.object(IOSDevice, "open") - def test_file_copy_fail(self, mock_open, mock_ft): + def test_file_copy_fail(self, mock_open, mock_ft, mock_verify_file, mock_get_local_checksum): self.device.native.send_command_timing.side_effect = None self.device.native.send_command.return_value = "flash: /dev/null" mock_ft_instance = mock_ft.return_value mock_ft_instance.transfer_file.side_effect = Exception - mock_ft_instance.check_file_exists.return_value = False + mock_verify_file.return_value = False with self.assertRaises(ios_module.FileTransferError): self.device.file_copy("source_file") mock_open.assert_not_called() + @mock.patch.object(IOSDevice, "get_local_checksum") + @mock.patch.object(IOSDevice, "verify_file") @mock.patch("pyntc.devices.ios_device.FileTransfer", autospec=True) @mock.patch.object(IOSDevice, "open") - def test_file_copy_socket_closed_good_md5(self, mock_open, mock_ft): + def test_file_copy_socket_closed_good_md5(self, mock_open, mock_ft, mock_verify_file, mock_get_local_checksum): self.device.native.send_command_timing.side_effect = None self.device.native.send_command.return_value = "flash: /dev/null" mock_ft_instance = mock_ft.return_value mock_ft_instance.transfer_file.side_effect = OSError - mock_ft_instance.check_file_exists.side_effect = [False, True] - mock_ft_instance.compare_md5.side_effect = [True, True] + mock_verify_file.side_effect = [False, True] + mock_ft_instance.compare_md5.return_value = True self.device.file_copy("path/to/source_file") @@ -199,17 +209,21 @@ def test_file_copy_socket_closed_good_md5(self, mock_open, mock_ft): mock_ft_instance.enable_scp.assert_any_call() mock_ft_instance.establish_scp_conn.assert_any_call() mock_ft_instance.transfer_file.assert_any_call() - mock_ft_instance.compare_md5.assert_has_calls([mock.call(), mock.call()]) + mock_ft_instance.compare_md5.assert_has_calls([mock.call()]) mock_open.assert_called_once() + @mock.patch.object(IOSDevice, "get_local_checksum") + @mock.patch.object(IOSDevice, "verify_file") @mock.patch("pyntc.devices.ios_device.FileTransfer", autospec=True) @mock.patch.object(IOSDevice, "open") - def test_file_copy_fail_socket_closed_bad_md5(self, mock_open, mock_ft): + def test_file_copy_fail_socket_closed_bad_md5(self, mock_open, mock_ft, mock_verify_file, mock_get_local_checksum): self.device.native.send_command_timing.side_effect = None self.device.native.send_command.return_value = "flash: /dev/null" mock_ft_instance = mock_ft.return_value mock_ft_instance.transfer_file.side_effect = OSError mock_ft_instance.check_file_exists.return_value = False + mock_verify_file.return_value = False + mock_get_local_checksum.return_value = "dummy_checksum" mock_ft_instance.compare_md5.return_value = False with self.assertRaises(ios_module.SocketClosedError): @@ -413,6 +427,173 @@ def test_install_os_not_enough_space( mock_wait.assert_not_called() mock_reboot.assert_not_called() + def test_get_local_checksum(self): + # Create a temporary file with known content + test_file_path = "test_file.txt" + test_content = "This is a test file for checksum." + with open(test_file_path, "w") as f: + f.write(test_content) + + import hashlib + + with self.subTest("Test get_local_checksum returns correct md5 checksum"): + expected_checksum = hashlib.md5(test_content.encode()).hexdigest() + actual_checksum = self.device.get_local_checksum(test_file_path) + self.assertEqual(actual_checksum, expected_checksum) + + with self.subTest("Test get_local_checksum returns correct sha512 checksum"): + expected_checksum = hashlib.sha512(test_content.encode()).hexdigest() + actual_checksum = self.device.get_local_checksum(test_file_path, hashing_algorithm="sha512") + self.assertEqual(actual_checksum, expected_checksum) + + with self.subTest("Test get_local_checksum with add_newline=True"): + expected_checksum = hashlib.md5((test_content + "\n").encode()).hexdigest() + actual_checksum = self.device.get_local_checksum(test_file_path, add_newline=True) + self.assertEqual(actual_checksum, expected_checksum) + + with self.subTest("Test get_local_checksum with invalid hashing algorithm"): + with self.assertRaises(ValueError): + self.device.get_local_checksum(test_file_path, hashing_algorithm="invalid_algo") + + # Clean up the temporary file + os.remove(test_file_path) + + def test_check_file_exists(self): + with self.subTest("Test check_file_exists returns True when file exists"): + self.device.native.send_command_expect.side_effect = None + self.device.native.send_command.return_value = "Directory of flash:/file.txt\n" + self.assertTrue(self.device.check_file_exists("file.txt", file_system="flash:")) + + with self.subTest("Test check_file_exists returns False when file does not exist"): + self.device.native.send_command_expect.side_effect = None + self.device.native.send_command.return_value = "%Error opening flash:/file.txt\n" + self.assertFalse(self.device.check_file_exists("file.txt", file_system="flash:")) + + def test_get_remote_checksum(self): + with self.subTest("Test get_remote_checksum returns correct md5 checksum"): + self.device.native.send_command_timing.side_effect = None + self.device.native.send_command_timing.return_value = "MD5 (flash:/file.txt) = dummy_checksum" + self.assertEqual(self.device.get_remote_checksum("file.txt", file_system="flash:"), "dummy_checksum") + + with self.subTest("Test get_remote_checksum with invalid hashing algorithm"): + with self.assertRaises(ValueError): + self.device.get_remote_checksum("file.txt", hashing_algorithm="invalid_algo", file_system="flash:") + + @mock.patch.object(IOSDevice, "verify_file") + def test_remote_file_copy_success(self, mock_verify): + # Setup file model + src = FileCopyModel( + download_url="sftp://user:test@1.1.1.1/test.bin", + checksum="12345", + file_name="test.bin", + hashing_algorithm="md5", + timeout=900, + ) + self.assertEqual(src.clean_url, "sftp://1.1.1.1/test.bin") + self.assertEqual(src.username, "user") + self.assertEqual(src.token, "test") + + # Scenario: First verify_file fails (needs copy), second verify_file succeeds (copy worked) + mock_verify.side_effect = [False, True] + + # Simulate the prompt sequence: + # 1. Initial command -> gets "Address or name of remote host" + # 2. Responds with "" -> gets "Copy complete" + self.device.native.send_command.side_effect = [ + "Address or name of remote host [1.1.1.1]?", + "Source username?", + "Password:", + "123456 bytes copied in 10.2 secs. Copy complete.", + ] + self.device.native.find_prompt.return_value = "Router#" + + with self.subTest("Test successful copy with interactive prompts"): + self.device.remote_file_copy(src, dest="test.bin", file_system="flash:") + + # Verify the command sent was correct + self.device.native.send_command.assert_any_call( + "copy sftp://1.1.1.1/test.bin flash:test.bin", expect_string=mock.ANY, read_timeout=900 + ) + self.device.native.send_command.assert_has_calls( + [ + mock.call("copy sftp://1.1.1.1/test.bin flash:test.bin", expect_string=mock.ANY, read_timeout=900), + mock.call( + "", expect_string=mock.ANY, read_timeout=900, cmd_verify=True + ), # Respond to "Address or name of remote host" + mock.call( + "user", expect_string=mock.ANY, read_timeout=900, cmd_verify=True + ), # Respond to "Source username?" + mock.call( + "test", expect_string=mock.ANY, read_timeout=900, cmd_verify=False + ), # Respond to "Password:" + ] + ) + + @mock.patch.object(IOSDevice, "verify_file") + def test_remote_file_copy_no_dest(self, mock_verify): + # Setup file model + src = FileCopyModel( + download_url="sftp://1.1.1.1/test.bin", + checksum="12345", + file_name="test.bin", + hashing_algorithm="md5", + timeout=300, + ) + self.assertEqual(src.clean_url, src.download_url) + self.assertIsNone(src.username) + self.assertIsNone(src.token) + + # Scenario: First verify_file fails (needs copy), second verify_file succeeds (copy worked) + mock_verify.side_effect = [False, True] + + # Simulate the prompt sequence: + # 1. Initial command -> gets "Address or name of remote host" + # 2. Responds with "" -> gets "Copy complete" + self.device.native.send_command.side_effect = [ + "Address or name of remote host [1.1.1.1]?", + "123456 bytes copied in 10.2 secs. Copy complete.", + ] + self.device.native.find_prompt.return_value = "Router#" + + with self.subTest("Test successful copy with interactive prompts"): + self.device.remote_file_copy(src, file_system="flash:") + + # Verify the command sent was correct + self.device.native.send_command.assert_has_calls( + [ + mock.call("copy sftp://1.1.1.1/test.bin flash:test.bin", expect_string=mock.ANY, read_timeout=300), + mock.call( + "", expect_string=mock.ANY, read_timeout=300, cmd_verify=True + ), # Respond to "Address or name of remote host" + ] + ) + + def test_remote_file_copy_type_error(self): + with self.subTest("Test raises TypeError if src is not FileCopyModel"): + with self.assertRaises(TypeError): + self.device.remote_file_copy("not_a_model") + + @mock.patch.object(IOSDevice, "verify_file") + def test_remote_file_copy_failure_on_error_output(self, mock_verify): + # Setup file model + src = FileCopyModel( + download_url="tftp://1.1.1.1/test.bin", + checksum="12345", + file_name="test.bin", + hashing_algorithm="md5", + ) + mock_verify.return_value = False + self.device.native.find_prompt.return_value = "Router#" + + # Simulate a network error returned by the device + self.device.native.send_command.return_value = "%Error opening tftp://1.1.1.1/test.bin (Timed out)" + + with self.subTest("Test raises FileTransferError when device returns Error string"): + from pyntc.errors import FileTransferError + + with self.assertRaises(FileTransferError): + self.device.remote_file_copy(src) + if __name__ == "__main__": unittest.main() From 3c88c27bdbf7d0b65122f9546712d40f810b6784 Mon Sep 17 00:00:00 2001 From: Jeff Kala <48843785+jeffkala@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:52:40 -0700 Subject: [PATCH 08/10] Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool (#343) * Cookie initially baked targeting develop by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "dir": "python", "ref": "main", "path": null } ``` Cookie: ``` { "remote": "https://github.com/networktocode/pyntc.git", "path": "/Users/jeffkala/Documents/GitHub/outputs/pyntc", "repository_path": "/Users/jeffkala/Documents/GitHub/outputs/pyntc", "dir": "", "branch_prefix": "drift-manager/develop", "context": { "codeowner_github_usernames": "@jeffkala @pszulczewski @pke11y", "full_name": "Network to Code, LLC", "email": "info@networktocode.com", "github_org": "networktocode", "description": "Python library focused on tasks related to device level and OS management.", "project_name": "pyntc", "project_slug": "pyntc", "repo_url": "https://github.com/networktocode/pyntc", "base_url": "pyntc", "project_python_name": "pyntc", "project_python_base_version": "3.10", "project_with_config_settings": "no", "generate_docs": "no", "version": "2.0.2", "original_publish_year": "2016", "_template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "_output_dir": "/Users/jeffkala/Documents/GitHub/outputs", "_repo_dir": "/Users/jeffkala/.cookiecutters/cookiecutter-ntc/python", "_checkout": "main" }, "drift_managed_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [], "baked_commit_ref": "", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": true, "json_filename": "", "output_dir": "../outputs", "push": true, "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "template_dir": "python", "template_ref": "main", "pull_request": null, "post_action": [], "disable_post_actions": false, "draft": null, "drift_managed_branch": "develop" } ``` * address initial drift manager conflicts * final few fixes for drift management * last few fixes * remove doctest as it was never implemented for this lib --- .cookiecutter.json | 30 ++ .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/workflows/ci.yml | 184 ++++------- .github/workflows/release.yml | 106 +++++++ .gitignore | 2 + .readthedocs.yml | 21 +- Dockerfile | 6 +- LICENSE | 4 +- README.md | 9 +- changes/+main.housekeeping | 1 + changes/.gitignore | 1 + docs/dev/dev_environment.md | 2 +- example.invoke.yml | 6 +- mkdocs.yml | 24 +- poetry.lock | 308 +++++++++++++------ pyntc/__init__.py | 6 +- pyproject.toml | 41 +-- tasks.py | 96 +++++- tests/integration/__init__.py | 1 + tests/unit/__init__.py | 1 + tests/unit/{test_basic.py => test_basics.py} | 21 +- towncrier_template.j2 | 43 +++ 24 files changed, 611 insertions(+), 310 deletions(-) create mode 100644 .cookiecutter.json create mode 100644 .github/workflows/release.yml create mode 100644 changes/+main.housekeeping create mode 100644 changes/.gitignore create mode 100644 tests/integration/__init__.py create mode 100644 tests/unit/__init__.py rename tests/unit/{test_basic.py => test_basics.py} (54%) create mode 100644 towncrier_template.j2 diff --git a/.cookiecutter.json b/.cookiecutter.json new file mode 100644 index 00000000..9fb4b1d8 --- /dev/null +++ b/.cookiecutter.json @@ -0,0 +1,30 @@ +{ + "cookiecutter": { + "codeowner_github_usernames": "@jeffkala @pszulczewski @pke11y", + "full_name": "Network to Code, LLC", + "email": "info@networktocode.com", + "github_org": "networktocode", + "description": "Python library focused on tasks related to device level and OS management.", + "project_name": "pyntc", + "project_slug": "pyntc", + "repo_url": "https://github.com/networktocode/pyntc", + "base_url": "pyntc", + "project_python_name": "pyntc", + "project_python_base_version": "3.10", + "project_with_config_settings": "no", + "generate_docs": "no", + "version": "2.0.2", + "original_publish_year": "2015", + "_drift_manager": { + "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", + "template_dir": "python", + "template_ref": "main", + "cookie_dir": "", + "pull_request_strategy": "create", + "post_actions": [], + "draft": false, + "baked_commit_ref": "a5add2662f26a3b877635bbf1fd787682414d2bf", + "drift_managed_branch": "develop" + } + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e4553f13..fcb97c51 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # Default owner(s) of all files in this repository -* @jeffkala @pszulczewski @pke11y \ No newline at end of file +* @jeffkala @pszulczewski @pke11y diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6f921a6c..a5e70455 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,8 +4,8 @@ about: Report a reproducible bug in the current release of pyntc --- ### Environment -* Python version: -* pyntc version: +* Python version: +* pyntc version: ### Expected Behavior diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index f0c71b9e..cf992e47 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,7 +5,7 @@ about: Propose a new feature or enhancement --- ### Environment -* pyntc version: +* pyntc version: " issue_format = "[#{issue}](https://github.com/networktocode/pyntc/issues/{issue})" +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true + [[tool.towncrier.type]] directory = "security" name = "Security" @@ -218,4 +223,4 @@ showcontent = true [[tool.towncrier.type]] directory = "housekeeping" name = "Housekeeping" -showcontent = true \ No newline at end of file +showcontent = true diff --git a/tasks.py b/tasks.py index a9624a7c..635e5a4b 100644 --- a/tasks.py +++ b/tasks.py @@ -1,5 +1,9 @@ """Tasks for use with Invoke.""" +import os +import re +from pathlib import Path + from invoke import Collection, Exit from invoke import task as invoke_task @@ -7,8 +11,7 @@ def is_truthy(arg): """Convert "truthy" strings into Booleans. - Examples - -------- + Examples: >>> is_truthy('yes') True Args: @@ -33,11 +36,11 @@ def is_truthy(arg): { "pyntc": { "project_name": "pyntc", - "python_ver": "3.12", - "local": False, + "python_ver": "3.10", + "local": is_truthy(os.getenv("INVOKE_PYNTC_LOCAL", "false")), "image_name": "pyntc", - "image_ver": "latest", - "pwd": ".", + "image_ver": os.getenv("INVOKE_PYNTC_IMAGE_VER", "latest"), + "pwd": Path(__file__).parent, } } ) @@ -93,6 +96,9 @@ def run_command(context, exec_cmd, port=None): return result +# ------------------------------------------------------------------------------ +# BUILD +# ------------------------------------------------------------------------------ @task( help={ "cache": "Whether to use Docker's cache when building images (default enabled)", @@ -115,6 +121,26 @@ def build(context, cache=True, force_rm=False, hide=False): print(f"Failed to build image {context.pyntc.image_name}:{context.pyntc.image_ver}\nError: {result.stderr}") +@task +def generate_packages(context): + """Generate all Python packages inside docker and copy the file locally under dist/.""" + command = "poetry build" + run_command(context, command) + + +@task( + help={ + "check": ( + "If enabled, check for outdated dependencies in the poetry.lock file, " + "instead of generating a new one. (default: disabled)" + ) + } +) +def lock(context, check=False): + """Generate poetry.lock inside the library container.""" + run_command(context, f"poetry {'check' if check else 'lock --no-update'}") + + @task def clean(context): """Remove the project specific image.""" @@ -131,9 +157,18 @@ def rebuild(context): @task -def pytest(context, args=""): +def coverage(context): + """Run the coverage report against pytest.""" + exec_cmd = "coverage run --source=pyntc -m pytest" + run_command(context, exec_cmd) + run_command(context, "coverage report") + run_command(context, "coverage html") + + +@task +def pytest(context): """Run pytest test cases.""" - exec_cmd = f"pytest {args}" + exec_cmd = "coverage run --source=pyntc -m pytest && coverage report" run_command(context, exec_cmd) @@ -215,25 +250,58 @@ def cli(context): context.run(f"{dev}", pty=True) -@task -def tests(context): +@task( + help={ + "lint-only": "Only run linters; unit tests will be excluded. (default: False)", + } +) +def tests(context, lint_only=False): """Run all tests for the specified name and Python version. Args: context (obj): Used to run specific commands + lint_only (bool): If True, only run linters and skip unit tests. """ + # If we are not running locally, start the docker containers so we don't have to for each test + # Sorted loosely from fastest to slowest + print("Running ruff...") ruff(context) - pylint(context) + print("Running yamllint...") yamllint(context) - pytest(context) - + print("Running poetry check...") + lock(context, check=True) + print("Running pylint...") + pylint(context) + print("Running mkdocs...") + build_and_check_docs(context) + if not lint_only: + print("Running unit tests...") + pytest(context) print("All tests have passed!") +@task +def build_and_check_docs(context): + """Build documentation and test the configuration.""" + command = "mkdocs build --no-directory-urls --strict" + run_command(context, command) + + # Check for the existence of a release notes file for the current version if it's not a prerelease. + version = context.run("poetry version --short", hide=True) + match = re.match(r"^(\d+)\.(\d+)\.\d+$", version.stdout.strip()) + if match: + major = match.group(1) + minor = match.group(2) + release_notes_file = Path(__file__).parent / "docs" / "admin" / "release_notes" / f"version_{major}.{minor}.md" + if not release_notes_file.exists(): + print(f"Release notes file `version_{major}.{minor}.md` does not exist.") + raise Exit(code=1) + + @task def docs(context): """Build and serve docs locally for development.""" - exec_cmd = "mkdocs serve -v --dev-addr=0.0.0.0:8001" + exec_cmd = "mkdocs serve -v" run_command(context, exec_cmd, port="8001:8001") diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..c66cd71b --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +"""Integration tests package.""" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..ea3f8b92 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +"""Unit tests package.""" diff --git a/tests/unit/test_basic.py b/tests/unit/test_basics.py similarity index 54% rename from tests/unit/test_basic.py rename to tests/unit/test_basics.py index bbe72654..d8f73c06 100644 --- a/tests/unit/test_basic.py +++ b/tests/unit/test_basics.py @@ -1,4 +1,4 @@ -"""Basic tests that do not require Django.""" +"""Basic tests that do not require Pyntc.""" import os import re @@ -7,25 +7,6 @@ import toml -class TestDocsPackaging(unittest.TestCase): - """Test Version in doc requirements is the same pyproject.""" - - def test_version(self): - """Verify that pyproject.toml dev dependencies have the same versions as in the docs requirements.txt.""" - parent_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - poetry_path = os.path.join(parent_path, "pyproject.toml") - poetry_details = toml.load(poetry_path)["tool"]["poetry"]["group"]["dev"]["dependencies"] - with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: - requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] - for pkg in requirements: - package_name = pkg - if len(pkg.split("==")) == 2: # noqa: PLR2004 - package_name, version = pkg.split("==") - else: - version = "*" - self.assertEqual(poetry_details[package_name], version) - - class TestDocsReleaseNotes(unittest.TestCase): """Test that mkdocs has the release notes for the current version.""" diff --git a/towncrier_template.j2 b/towncrier_template.j2 new file mode 100644 index 00000000..f69a5668 --- /dev/null +++ b/towncrier_template.j2 @@ -0,0 +1,43 @@ + +# v{{ versiondata.version.split(".")[:2] | join(".") }} Release Notes + +This document describes all new features and changes in the release. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Major features or milestones +- Changes to compatibility with Nautobot and/or other apps, libraries etc. + +{% if render_title %} +## [v{{ versiondata.version }} ({{ versiondata.date }})](https://github.com/networktocode/pyntc/releases/tag/v{{ versiondata.version}}) + +{% endif %} +{% for section, _ in sections.items() %} +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section] %} +{% if sections[section][category]|length != 0 %} +### {{ definitions[category]['name'] }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +{% for item in text.split('\n') %} +{% if values %} +- {{ values|join(', ') }} - {{ item.strip() }} +{% else %} +- {{ item.strip() }} +{% endif %} +{% endfor %} +{% endfor %} + +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% endif %} +{% endfor %} +{% else %} +No significant changes. + +{% endif %} +{% endfor %} + From 7d2a04dfba8efe40f66f3f4571f135c81a99ca23 Mon Sep 17 00:00:00 2001 From: Jeff Kala <48843785+jeffkala@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:16:45 -0700 Subject: [PATCH 09/10] fix docs test failing after new model adds (#347) * fix docs test failing after new model adds * fix docs test failing after new model adds * remove incorrectly commited html files --- .github/workflows/release.yml | 2 +- .gitignore | 1 + changes/+docs-fixes.housekeeping | 1 + mkdocs.yml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changes/+docs-fixes.housekeeping diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dffc187..7f8a7c15 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ --- name: "Release" -on: # yamllint disable-line rule:truthy rule:comments +on: # yamllint disable-line rule:truthy rule:comments release: types: ["published"] diff --git a/.gitignore b/.gitignore index fabcfd8d..b770b141 100644 --- a/.gitignore +++ b/.gitignore @@ -308,3 +308,4 @@ docs/CHANGELOG.md public /compose.yaml /dump.sql +/pyntc/static/pyntc/docs diff --git a/changes/+docs-fixes.housekeeping b/changes/+docs-fixes.housekeeping new file mode 100644 index 00000000..4dc90e2a --- /dev/null +++ b/changes/+docs-fixes.housekeeping @@ -0,0 +1 @@ +Fixed docs build and code-reference issues. diff --git a/mkdocs.yml b/mkdocs.yml index 3d8a3c19..603b6ad8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -171,4 +171,5 @@ nav: - pyntc.utils: "code-reference/pyntc/utils/__init__.md" - pyntc.utils.converters: "code-reference/pyntc/utils/converters.md" - pyntc.utils.templates: "code-reference/pyntc/utils/templates/__init__.md" + - pyntc.utils.models: "code-reference/pyntc/utils/models.md" - pyntc.arch_decision: "dev/arch_decision.md" From e2fc0f55e001c35967fa1822f1064287c2142dc0 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Tue, 3 Mar 2026 11:40:35 -0700 Subject: [PATCH 10/10] Release v2.1.0 --- changes/+docs-fixes.housekeeping | 1 - changes/+main.housekeeping | 1 - changes/335.housekeeping | 3 --- changes/345.added | 1 - docs/admin/release_notes/version_2.1.md | 22 ++++++++++++++++++++++ mkdocs.yml | 1 + pyproject.toml | 2 +- 7 files changed, 24 insertions(+), 7 deletions(-) delete mode 100644 changes/+docs-fixes.housekeeping delete mode 100644 changes/+main.housekeeping delete mode 100644 changes/335.housekeeping delete mode 100644 changes/345.added create mode 100644 docs/admin/release_notes/version_2.1.md diff --git a/changes/+docs-fixes.housekeeping b/changes/+docs-fixes.housekeeping deleted file mode 100644 index 4dc90e2a..00000000 --- a/changes/+docs-fixes.housekeeping +++ /dev/null @@ -1 +0,0 @@ -Fixed docs build and code-reference issues. diff --git a/changes/+main.housekeeping b/changes/+main.housekeeping deleted file mode 100644 index 3433adf6..00000000 --- a/changes/+main.housekeeping +++ /dev/null @@ -1 +0,0 @@ -Rebaked from the cookie `main`. diff --git a/changes/335.housekeeping b/changes/335.housekeeping deleted file mode 100644 index 4be5b694..00000000 --- a/changes/335.housekeeping +++ /dev/null @@ -1,3 +0,0 @@ -Replaced black, bandit, flake8 and pydocstyle with ruff. -Updated tasks.py with newest task list. -Updated to using pyinvoke for development environment definition. diff --git a/changes/345.added b/changes/345.added deleted file mode 100644 index f7a7e976..00000000 --- a/changes/345.added +++ /dev/null @@ -1 +0,0 @@ -Added the ability to download files from within a Cisco IOS device. diff --git a/docs/admin/release_notes/version_2.1.md b/docs/admin/release_notes/version_2.1.md new file mode 100644 index 00000000..02e86fa5 --- /dev/null +++ b/docs/admin/release_notes/version_2.1.md @@ -0,0 +1,22 @@ + +# v2.1 Release Notes + +This document describes all new features and changes in the release. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Release Overview + +- Adding the ability to perform file downloads in Cisco IOS. This enhances OS Upgrades and file operations greatly! + +## [v2.1.0 (2026-03-03)](https://github.com/networktocode/pyntc/releases/tag/v2.1.0) + +### Added + +- [#345](https://github.com/networktocode/pyntc/issues/345) - Added the ability to download files from within a Cisco IOS device. + +### Housekeeping + +- [#335](https://github.com/networktocode/pyntc/issues/335) - Replaced black, bandit, flake8 and pydocstyle with ruff. +- [#335](https://github.com/networktocode/pyntc/issues/335) - Updated tasks.py with newest task list. +- [#335](https://github.com/networktocode/pyntc/issues/335) - Updated to using pyinvoke for development environment definition. +- Fixed docs build and code-reference issues. +- Rebaked from the cookie `main`. diff --git a/mkdocs.yml b/mkdocs.yml index 603b6ad8..bf706005 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -142,6 +142,7 @@ nav: - v0.20: "admin/release_notes/version_0.20.md" - v1.0: "admin/release_notes/version_1.0.md" - v2.0: "admin/release_notes/version_2.0.md" + - v2.1: "admin/release_notes/version_2.1.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" diff --git a/pyproject.toml b/pyproject.toml index 2fe542e5..65c1a775 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyntc" -version = "2.0.2" +version = "2.1.0" description = "Python library focused on tasks related to device level and OS management." authors = ["Network to Code, LLC "] readme = "README.md"